diff options
author | Jan Engelhardt <jengelh@computergmbh.de> | 2008-10-08 05:35:00 -0400 |
---|---|---|
committer | Patrick McHardy <kaber@trash.net> | 2008-10-08 05:35:00 -0400 |
commit | e948b20a71a06a740c925d6ea22b59b4e17cfa0c (patch) | |
tree | 6298c43d1b6aa424c47e1dec8dfd3d932b813dcc /net/netfilter/xt_recent.c | |
parent | 76108cea065cda58366d16a7eb6ca90d717a1396 (diff) |
netfilter: rename ipt_recent to xt_recent
Like with other modules (such as ipt_state), ipt_recent.h is changed
to forward definitions to (IOW include) xt_recent.h, and xt_recent.c
is changed to use the new constant names.
Signed-off-by: Jan Engelhardt <jengelh@medozas.de>
Signed-off-by: Patrick McHardy <kaber@trash.net>
Diffstat (limited to 'net/netfilter/xt_recent.c')
-rw-r--r-- | net/netfilter/xt_recent.c | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/net/netfilter/xt_recent.c b/net/netfilter/xt_recent.c new file mode 100644 index 00000000000..422c0e4d66b --- /dev/null +++ b/net/netfilter/xt_recent.c | |||
@@ -0,0 +1,502 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2006 Patrick McHardy <kaber@trash.net> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | * | ||
8 | * This is a replacement of the old ipt_recent module, which carried the | ||
9 | * following copyright notice: | ||
10 | * | ||
11 | * Author: Stephen Frost <sfrost@snowman.net> | ||
12 | * Copyright 2002-2003, Stephen Frost, 2.5.x port by laforge@netfilter.org | ||
13 | */ | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/ip.h> | ||
16 | #include <linux/moduleparam.h> | ||
17 | #include <linux/proc_fs.h> | ||
18 | #include <linux/seq_file.h> | ||
19 | #include <linux/string.h> | ||
20 | #include <linux/ctype.h> | ||
21 | #include <linux/list.h> | ||
22 | #include <linux/random.h> | ||
23 | #include <linux/jhash.h> | ||
24 | #include <linux/bitops.h> | ||
25 | #include <linux/skbuff.h> | ||
26 | #include <linux/inet.h> | ||
27 | #include <net/net_namespace.h> | ||
28 | |||
29 | #include <linux/netfilter/x_tables.h> | ||
30 | #include <linux/netfilter/xt_recent.h> | ||
31 | |||
32 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); | ||
33 | MODULE_DESCRIPTION("Xtables: \"recently-seen\" host matching for IPv4"); | ||
34 | MODULE_LICENSE("GPL"); | ||
35 | MODULE_ALIAS("ipt_recent"); | ||
36 | |||
37 | static unsigned int ip_list_tot = 100; | ||
38 | static unsigned int ip_pkt_list_tot = 20; | ||
39 | static unsigned int ip_list_hash_size = 0; | ||
40 | static unsigned int ip_list_perms = 0644; | ||
41 | static unsigned int ip_list_uid = 0; | ||
42 | static unsigned int ip_list_gid = 0; | ||
43 | module_param(ip_list_tot, uint, 0400); | ||
44 | module_param(ip_pkt_list_tot, uint, 0400); | ||
45 | module_param(ip_list_hash_size, uint, 0400); | ||
46 | module_param(ip_list_perms, uint, 0400); | ||
47 | module_param(ip_list_uid, uint, 0400); | ||
48 | module_param(ip_list_gid, uint, 0400); | ||
49 | MODULE_PARM_DESC(ip_list_tot, "number of IPs to remember per list"); | ||
50 | MODULE_PARM_DESC(ip_pkt_list_tot, "number of packets per IP to remember (max. 255)"); | ||
51 | MODULE_PARM_DESC(ip_list_hash_size, "size of hash table used to look up IPs"); | ||
52 | MODULE_PARM_DESC(ip_list_perms, "permissions on /proc/net/ipt_recent/* files"); | ||
53 | MODULE_PARM_DESC(ip_list_uid,"owner of /proc/net/ipt_recent/* files"); | ||
54 | MODULE_PARM_DESC(ip_list_gid,"owning group of /proc/net/ipt_recent/* files"); | ||
55 | |||
56 | struct recent_entry { | ||
57 | struct list_head list; | ||
58 | struct list_head lru_list; | ||
59 | __be32 addr; | ||
60 | u_int8_t ttl; | ||
61 | u_int8_t index; | ||
62 | u_int16_t nstamps; | ||
63 | unsigned long stamps[0]; | ||
64 | }; | ||
65 | |||
66 | struct recent_table { | ||
67 | struct list_head list; | ||
68 | char name[XT_RECENT_NAME_LEN]; | ||
69 | #ifdef CONFIG_PROC_FS | ||
70 | struct proc_dir_entry *proc; | ||
71 | #endif | ||
72 | unsigned int refcnt; | ||
73 | unsigned int entries; | ||
74 | struct list_head lru_list; | ||
75 | struct list_head iphash[0]; | ||
76 | }; | ||
77 | |||
78 | static LIST_HEAD(tables); | ||
79 | static DEFINE_SPINLOCK(recent_lock); | ||
80 | static DEFINE_MUTEX(recent_mutex); | ||
81 | |||
82 | #ifdef CONFIG_PROC_FS | ||
83 | static struct proc_dir_entry *proc_dir; | ||
84 | static const struct file_operations recent_fops; | ||
85 | #endif | ||
86 | |||
87 | static u_int32_t hash_rnd; | ||
88 | static int hash_rnd_initted; | ||
89 | |||
90 | static unsigned int recent_entry_hash(__be32 addr) | ||
91 | { | ||
92 | if (!hash_rnd_initted) { | ||
93 | get_random_bytes(&hash_rnd, 4); | ||
94 | hash_rnd_initted = 1; | ||
95 | } | ||
96 | return jhash_1word((__force u32)addr, hash_rnd) & (ip_list_hash_size - 1); | ||
97 | } | ||
98 | |||
99 | static struct recent_entry * | ||
100 | recent_entry_lookup(const struct recent_table *table, __be32 addr, u_int8_t ttl) | ||
101 | { | ||
102 | struct recent_entry *e; | ||
103 | unsigned int h; | ||
104 | |||
105 | h = recent_entry_hash(addr); | ||
106 | list_for_each_entry(e, &table->iphash[h], list) | ||
107 | if (e->addr == addr && (ttl == e->ttl || !ttl || !e->ttl)) | ||
108 | return e; | ||
109 | return NULL; | ||
110 | } | ||
111 | |||
112 | static void recent_entry_remove(struct recent_table *t, struct recent_entry *e) | ||
113 | { | ||
114 | list_del(&e->list); | ||
115 | list_del(&e->lru_list); | ||
116 | kfree(e); | ||
117 | t->entries--; | ||
118 | } | ||
119 | |||
120 | static struct recent_entry * | ||
121 | recent_entry_init(struct recent_table *t, __be32 addr, u_int8_t ttl) | ||
122 | { | ||
123 | struct recent_entry *e; | ||
124 | |||
125 | if (t->entries >= ip_list_tot) { | ||
126 | e = list_entry(t->lru_list.next, struct recent_entry, lru_list); | ||
127 | recent_entry_remove(t, e); | ||
128 | } | ||
129 | e = kmalloc(sizeof(*e) + sizeof(e->stamps[0]) * ip_pkt_list_tot, | ||
130 | GFP_ATOMIC); | ||
131 | if (e == NULL) | ||
132 | return NULL; | ||
133 | e->addr = addr; | ||
134 | e->ttl = ttl; | ||
135 | e->stamps[0] = jiffies; | ||
136 | e->nstamps = 1; | ||
137 | e->index = 1; | ||
138 | list_add_tail(&e->list, &t->iphash[recent_entry_hash(addr)]); | ||
139 | list_add_tail(&e->lru_list, &t->lru_list); | ||
140 | t->entries++; | ||
141 | return e; | ||
142 | } | ||
143 | |||
144 | static void recent_entry_update(struct recent_table *t, struct recent_entry *e) | ||
145 | { | ||
146 | e->stamps[e->index++] = jiffies; | ||
147 | if (e->index > e->nstamps) | ||
148 | e->nstamps = e->index; | ||
149 | e->index %= ip_pkt_list_tot; | ||
150 | list_move_tail(&e->lru_list, &t->lru_list); | ||
151 | } | ||
152 | |||
153 | static struct recent_table *recent_table_lookup(const char *name) | ||
154 | { | ||
155 | struct recent_table *t; | ||
156 | |||
157 | list_for_each_entry(t, &tables, list) | ||
158 | if (!strcmp(t->name, name)) | ||
159 | return t; | ||
160 | return NULL; | ||
161 | } | ||
162 | |||
163 | static void recent_table_flush(struct recent_table *t) | ||
164 | { | ||
165 | struct recent_entry *e, *next; | ||
166 | unsigned int i; | ||
167 | |||
168 | for (i = 0; i < ip_list_hash_size; i++) | ||
169 | list_for_each_entry_safe(e, next, &t->iphash[i], list) | ||
170 | recent_entry_remove(t, e); | ||
171 | } | ||
172 | |||
173 | static bool | ||
174 | recent_mt(const struct sk_buff *skb, const struct net_device *in, | ||
175 | const struct net_device *out, const struct xt_match *match, | ||
176 | const void *matchinfo, int offset, unsigned int protoff, | ||
177 | bool *hotdrop) | ||
178 | { | ||
179 | const struct xt_recent_mtinfo *info = matchinfo; | ||
180 | struct recent_table *t; | ||
181 | struct recent_entry *e; | ||
182 | __be32 addr; | ||
183 | u_int8_t ttl; | ||
184 | bool ret = info->invert; | ||
185 | |||
186 | if (info->side == XT_RECENT_DEST) | ||
187 | addr = ip_hdr(skb)->daddr; | ||
188 | else | ||
189 | addr = ip_hdr(skb)->saddr; | ||
190 | |||
191 | ttl = ip_hdr(skb)->ttl; | ||
192 | /* use TTL as seen before forwarding */ | ||
193 | if (out && !skb->sk) | ||
194 | ttl++; | ||
195 | |||
196 | spin_lock_bh(&recent_lock); | ||
197 | t = recent_table_lookup(info->name); | ||
198 | e = recent_entry_lookup(t, addr, | ||
199 | info->check_set & XT_RECENT_TTL ? ttl : 0); | ||
200 | if (e == NULL) { | ||
201 | if (!(info->check_set & XT_RECENT_SET)) | ||
202 | goto out; | ||
203 | e = recent_entry_init(t, addr, ttl); | ||
204 | if (e == NULL) | ||
205 | *hotdrop = true; | ||
206 | ret = !ret; | ||
207 | goto out; | ||
208 | } | ||
209 | |||
210 | if (info->check_set & XT_RECENT_SET) | ||
211 | ret = !ret; | ||
212 | else if (info->check_set & XT_RECENT_REMOVE) { | ||
213 | recent_entry_remove(t, e); | ||
214 | ret = !ret; | ||
215 | } else if (info->check_set & (XT_RECENT_CHECK | XT_RECENT_UPDATE)) { | ||
216 | unsigned long time = jiffies - info->seconds * HZ; | ||
217 | unsigned int i, hits = 0; | ||
218 | |||
219 | for (i = 0; i < e->nstamps; i++) { | ||
220 | if (info->seconds && time_after(time, e->stamps[i])) | ||
221 | continue; | ||
222 | if (++hits >= info->hit_count) { | ||
223 | ret = !ret; | ||
224 | break; | ||
225 | } | ||
226 | } | ||
227 | } | ||
228 | |||
229 | if (info->check_set & XT_RECENT_SET || | ||
230 | (info->check_set & XT_RECENT_UPDATE && ret)) { | ||
231 | recent_entry_update(t, e); | ||
232 | e->ttl = ttl; | ||
233 | } | ||
234 | out: | ||
235 | spin_unlock_bh(&recent_lock); | ||
236 | return ret; | ||
237 | } | ||
238 | |||
239 | static bool | ||
240 | recent_mt_check(const char *tablename, const void *ip, | ||
241 | const struct xt_match *match, void *matchinfo, | ||
242 | unsigned int hook_mask) | ||
243 | { | ||
244 | const struct xt_recent_mtinfo *info = matchinfo; | ||
245 | struct recent_table *t; | ||
246 | unsigned i; | ||
247 | bool ret = false; | ||
248 | |||
249 | if (hweight8(info->check_set & | ||
250 | (XT_RECENT_SET | XT_RECENT_REMOVE | | ||
251 | XT_RECENT_CHECK | XT_RECENT_UPDATE)) != 1) | ||
252 | return false; | ||
253 | if ((info->check_set & (XT_RECENT_SET | XT_RECENT_REMOVE)) && | ||
254 | (info->seconds || info->hit_count)) | ||
255 | return false; | ||
256 | if (info->hit_count > ip_pkt_list_tot) | ||
257 | return false; | ||
258 | if (info->name[0] == '\0' || | ||
259 | strnlen(info->name, XT_RECENT_NAME_LEN) == XT_RECENT_NAME_LEN) | ||
260 | return false; | ||
261 | |||
262 | mutex_lock(&recent_mutex); | ||
263 | t = recent_table_lookup(info->name); | ||
264 | if (t != NULL) { | ||
265 | t->refcnt++; | ||
266 | ret = true; | ||
267 | goto out; | ||
268 | } | ||
269 | |||
270 | t = kzalloc(sizeof(*t) + sizeof(t->iphash[0]) * ip_list_hash_size, | ||
271 | GFP_KERNEL); | ||
272 | if (t == NULL) | ||
273 | goto out; | ||
274 | t->refcnt = 1; | ||
275 | strcpy(t->name, info->name); | ||
276 | INIT_LIST_HEAD(&t->lru_list); | ||
277 | for (i = 0; i < ip_list_hash_size; i++) | ||
278 | INIT_LIST_HEAD(&t->iphash[i]); | ||
279 | #ifdef CONFIG_PROC_FS | ||
280 | t->proc = proc_create(t->name, ip_list_perms, proc_dir, &recent_fops); | ||
281 | if (t->proc == NULL) { | ||
282 | kfree(t); | ||
283 | goto out; | ||
284 | } | ||
285 | t->proc->uid = ip_list_uid; | ||
286 | t->proc->gid = ip_list_gid; | ||
287 | t->proc->data = t; | ||
288 | #endif | ||
289 | spin_lock_bh(&recent_lock); | ||
290 | list_add_tail(&t->list, &tables); | ||
291 | spin_unlock_bh(&recent_lock); | ||
292 | ret = true; | ||
293 | out: | ||
294 | mutex_unlock(&recent_mutex); | ||
295 | return ret; | ||
296 | } | ||
297 | |||
298 | static void recent_mt_destroy(const struct xt_match *match, void *matchinfo) | ||
299 | { | ||
300 | const struct xt_recent_mtinfo *info = matchinfo; | ||
301 | struct recent_table *t; | ||
302 | |||
303 | mutex_lock(&recent_mutex); | ||
304 | t = recent_table_lookup(info->name); | ||
305 | if (--t->refcnt == 0) { | ||
306 | spin_lock_bh(&recent_lock); | ||
307 | list_del(&t->list); | ||
308 | spin_unlock_bh(&recent_lock); | ||
309 | #ifdef CONFIG_PROC_FS | ||
310 | remove_proc_entry(t->name, proc_dir); | ||
311 | #endif | ||
312 | recent_table_flush(t); | ||
313 | kfree(t); | ||
314 | } | ||
315 | mutex_unlock(&recent_mutex); | ||
316 | } | ||
317 | |||
318 | #ifdef CONFIG_PROC_FS | ||
319 | struct recent_iter_state { | ||
320 | struct recent_table *table; | ||
321 | unsigned int bucket; | ||
322 | }; | ||
323 | |||
324 | static void *recent_seq_start(struct seq_file *seq, loff_t *pos) | ||
325 | __acquires(recent_lock) | ||
326 | { | ||
327 | struct recent_iter_state *st = seq->private; | ||
328 | const struct recent_table *t = st->table; | ||
329 | struct recent_entry *e; | ||
330 | loff_t p = *pos; | ||
331 | |||
332 | spin_lock_bh(&recent_lock); | ||
333 | |||
334 | for (st->bucket = 0; st->bucket < ip_list_hash_size; st->bucket++) | ||
335 | list_for_each_entry(e, &t->iphash[st->bucket], list) | ||
336 | if (p-- == 0) | ||
337 | return e; | ||
338 | return NULL; | ||
339 | } | ||
340 | |||
341 | static void *recent_seq_next(struct seq_file *seq, void *v, loff_t *pos) | ||
342 | { | ||
343 | struct recent_iter_state *st = seq->private; | ||
344 | const struct recent_table *t = st->table; | ||
345 | struct recent_entry *e = v; | ||
346 | struct list_head *head = e->list.next; | ||
347 | |||
348 | while (head == &t->iphash[st->bucket]) { | ||
349 | if (++st->bucket >= ip_list_hash_size) | ||
350 | return NULL; | ||
351 | head = t->iphash[st->bucket].next; | ||
352 | } | ||
353 | (*pos)++; | ||
354 | return list_entry(head, struct recent_entry, list); | ||
355 | } | ||
356 | |||
357 | static void recent_seq_stop(struct seq_file *s, void *v) | ||
358 | __releases(recent_lock) | ||
359 | { | ||
360 | spin_unlock_bh(&recent_lock); | ||
361 | } | ||
362 | |||
363 | static int recent_seq_show(struct seq_file *seq, void *v) | ||
364 | { | ||
365 | const struct recent_entry *e = v; | ||
366 | unsigned int i; | ||
367 | |||
368 | i = (e->index - 1) % ip_pkt_list_tot; | ||
369 | seq_printf(seq, "src=%u.%u.%u.%u ttl: %u last_seen: %lu oldest_pkt: %u", | ||
370 | NIPQUAD(e->addr), e->ttl, e->stamps[i], e->index); | ||
371 | for (i = 0; i < e->nstamps; i++) | ||
372 | seq_printf(seq, "%s %lu", i ? "," : "", e->stamps[i]); | ||
373 | seq_printf(seq, "\n"); | ||
374 | return 0; | ||
375 | } | ||
376 | |||
377 | static const struct seq_operations recent_seq_ops = { | ||
378 | .start = recent_seq_start, | ||
379 | .next = recent_seq_next, | ||
380 | .stop = recent_seq_stop, | ||
381 | .show = recent_seq_show, | ||
382 | }; | ||
383 | |||
384 | static int recent_seq_open(struct inode *inode, struct file *file) | ||
385 | { | ||
386 | struct proc_dir_entry *pde = PDE(inode); | ||
387 | struct recent_iter_state *st; | ||
388 | |||
389 | st = __seq_open_private(file, &recent_seq_ops, sizeof(*st)); | ||
390 | if (st == NULL) | ||
391 | return -ENOMEM; | ||
392 | |||
393 | st->table = pde->data; | ||
394 | return 0; | ||
395 | } | ||
396 | |||
397 | static ssize_t recent_proc_write(struct file *file, const char __user *input, | ||
398 | size_t size, loff_t *loff) | ||
399 | { | ||
400 | const struct proc_dir_entry *pde = PDE(file->f_path.dentry->d_inode); | ||
401 | struct recent_table *t = pde->data; | ||
402 | struct recent_entry *e; | ||
403 | char buf[sizeof("+255.255.255.255")], *c = buf; | ||
404 | __be32 addr; | ||
405 | int add; | ||
406 | |||
407 | if (size > sizeof(buf)) | ||
408 | size = sizeof(buf); | ||
409 | if (copy_from_user(buf, input, size)) | ||
410 | return -EFAULT; | ||
411 | while (isspace(*c)) | ||
412 | c++; | ||
413 | |||
414 | if (size - (c - buf) < 5) | ||
415 | return c - buf; | ||
416 | if (!strncmp(c, "clear", 5)) { | ||
417 | c += 5; | ||
418 | spin_lock_bh(&recent_lock); | ||
419 | recent_table_flush(t); | ||
420 | spin_unlock_bh(&recent_lock); | ||
421 | return c - buf; | ||
422 | } | ||
423 | |||
424 | switch (*c) { | ||
425 | case '-': | ||
426 | add = 0; | ||
427 | c++; | ||
428 | break; | ||
429 | case '+': | ||
430 | c++; | ||
431 | default: | ||
432 | add = 1; | ||
433 | break; | ||
434 | } | ||
435 | addr = in_aton(c); | ||
436 | |||
437 | spin_lock_bh(&recent_lock); | ||
438 | e = recent_entry_lookup(t, addr, 0); | ||
439 | if (e == NULL) { | ||
440 | if (add) | ||
441 | recent_entry_init(t, addr, 0); | ||
442 | } else { | ||
443 | if (add) | ||
444 | recent_entry_update(t, e); | ||
445 | else | ||
446 | recent_entry_remove(t, e); | ||
447 | } | ||
448 | spin_unlock_bh(&recent_lock); | ||
449 | return size; | ||
450 | } | ||
451 | |||
452 | static const struct file_operations recent_fops = { | ||
453 | .open = recent_seq_open, | ||
454 | .read = seq_read, | ||
455 | .write = recent_proc_write, | ||
456 | .release = seq_release_private, | ||
457 | .owner = THIS_MODULE, | ||
458 | }; | ||
459 | #endif /* CONFIG_PROC_FS */ | ||
460 | |||
461 | static struct xt_match recent_mt_reg __read_mostly = { | ||
462 | .name = "recent", | ||
463 | .family = AF_INET, | ||
464 | .match = recent_mt, | ||
465 | .matchsize = sizeof(struct xt_recent_mtinfo), | ||
466 | .checkentry = recent_mt_check, | ||
467 | .destroy = recent_mt_destroy, | ||
468 | .me = THIS_MODULE, | ||
469 | }; | ||
470 | |||
471 | static int __init recent_mt_init(void) | ||
472 | { | ||
473 | int err; | ||
474 | |||
475 | if (!ip_list_tot || !ip_pkt_list_tot || ip_pkt_list_tot > 255) | ||
476 | return -EINVAL; | ||
477 | ip_list_hash_size = 1 << fls(ip_list_tot); | ||
478 | |||
479 | err = xt_register_match(&recent_mt_reg); | ||
480 | #ifdef CONFIG_PROC_FS | ||
481 | if (err) | ||
482 | return err; | ||
483 | proc_dir = proc_mkdir("ipt_recent", init_net.proc_net); | ||
484 | if (proc_dir == NULL) { | ||
485 | xt_unregister_match(&recent_mt_reg); | ||
486 | err = -ENOMEM; | ||
487 | } | ||
488 | #endif | ||
489 | return err; | ||
490 | } | ||
491 | |||
492 | static void __exit recent_mt_exit(void) | ||
493 | { | ||
494 | BUG_ON(!list_empty(&tables)); | ||
495 | xt_unregister_match(&recent_mt_reg); | ||
496 | #ifdef CONFIG_PROC_FS | ||
497 | remove_proc_entry("ipt_recent", init_net.proc_net); | ||
498 | #endif | ||
499 | } | ||
500 | |||
501 | module_init(recent_mt_init); | ||
502 | module_exit(recent_mt_exit); | ||