[SECMARK]: Add new packet controls to SELinux
[deliverable/linux.git] / net / ipv4 / netfilter / ipt_recent.c
CommitLineData
404bdbfd
PM
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/moduleparam.h>
1da177e4 16#include <linux/proc_fs.h>
404bdbfd
PM
17#include <linux/seq_file.h>
18#include <linux/string.h>
1da177e4 19#include <linux/ctype.h>
404bdbfd
PM
20#include <linux/list.h>
21#include <linux/random.h>
22#include <linux/jhash.h>
23#include <linux/bitops.h>
24#include <linux/skbuff.h>
25#include <linux/inet.h>
1da177e4
LT
26
27#include <linux/netfilter_ipv4/ip_tables.h>
28#include <linux/netfilter_ipv4/ipt_recent.h>
29
404bdbfd
PM
30MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
31MODULE_DESCRIPTION("IP tables recently seen matching module");
32MODULE_LICENSE("GPL");
1da177e4 33
e7be6994
PM
34static unsigned int ip_list_tot = 100;
35static unsigned int ip_pkt_list_tot = 20;
36static unsigned int ip_list_hash_size = 0;
37static unsigned int ip_list_perms = 0644;
e7be6994
PM
38module_param(ip_list_tot, uint, 0400);
39module_param(ip_pkt_list_tot, uint, 0400);
40module_param(ip_list_hash_size, uint, 0400);
41module_param(ip_list_perms, uint, 0400);
404bdbfd
PM
42MODULE_PARM_DESC(ip_list_tot, "number of IPs to remember per list");
43MODULE_PARM_DESC(ip_pkt_list_tot, "number of packets per IP to remember (max. 255)");
44MODULE_PARM_DESC(ip_list_hash_size, "size of hash table used to look up IPs");
45MODULE_PARM_DESC(ip_list_perms, "permissions on /proc/net/ipt_recent/* files");
46
47
48struct recent_entry {
49 struct list_head list;
50 struct list_head lru_list;
51 u_int32_t addr;
52 u_int8_t ttl;
53 u_int8_t index;
54 u_int16_t nstamps;
55 unsigned long stamps[0];
1da177e4
LT
56};
57
404bdbfd
PM
58struct recent_table {
59 struct list_head list;
60 char name[IPT_RECENT_NAME_LEN];
1da177e4 61#ifdef CONFIG_PROC_FS
404bdbfd
PM
62 struct proc_dir_entry *proc;
63#endif
64 unsigned int refcnt;
65 unsigned int entries;
66 struct list_head lru_list;
67 struct list_head iphash[0];
1da177e4
LT
68};
69
404bdbfd 70static LIST_HEAD(tables);
1da177e4
LT
71static DEFINE_SPINLOCK(recent_lock);
72
73#ifdef CONFIG_PROC_FS
404bdbfd
PM
74static struct proc_dir_entry *proc_dir;
75static struct file_operations recent_fops;
1da177e4
LT
76#endif
77
404bdbfd
PM
78static u_int32_t hash_rnd;
79static int hash_rnd_initted;
1da177e4 80
404bdbfd 81static unsigned int recent_entry_hash(u_int32_t addr)
1da177e4 82{
404bdbfd
PM
83 if (!hash_rnd_initted) {
84 get_random_bytes(&hash_rnd, 4);
85 hash_rnd_initted = 1;
1da177e4 86 }
404bdbfd 87 return jhash_1word(addr, hash_rnd) & (ip_list_hash_size - 1);
1da177e4
LT
88}
89
404bdbfd
PM
90static struct recent_entry *
91recent_entry_lookup(const struct recent_table *table, u_int32_t addr, u_int8_t ttl)
1da177e4 92{
404bdbfd
PM
93 struct recent_entry *e;
94 unsigned int h;
95
96 h = recent_entry_hash(addr);
97 list_for_each_entry(e, &table->iphash[h], list)
98 if (e->addr == addr && (ttl == e->ttl || !ttl || !e->ttl))
99 return e;
100 return NULL;
101}
1da177e4 102
404bdbfd
PM
103static void recent_entry_remove(struct recent_table *t, struct recent_entry *e)
104{
105 list_del(&e->list);
106 list_del(&e->lru_list);
107 kfree(e);
108 t->entries--;
109}
1da177e4 110
404bdbfd
PM
111static struct recent_entry *
112recent_entry_init(struct recent_table *t, u_int32_t addr, u_int8_t ttl)
113{
114 struct recent_entry *e;
1da177e4 115
404bdbfd
PM
116 if (t->entries >= ip_list_tot) {
117 e = list_entry(t->lru_list.next, struct recent_entry, lru_list);
118 recent_entry_remove(t, e);
1da177e4 119 }
404bdbfd
PM
120 e = kmalloc(sizeof(*e) + sizeof(e->stamps[0]) * ip_pkt_list_tot,
121 GFP_ATOMIC);
122 if (e == NULL)
123 return NULL;
124 e->addr = addr;
125 e->ttl = ttl;
126 e->stamps[0] = jiffies;
127 e->nstamps = 1;
128 e->index = 1;
129 list_add_tail(&e->list, &t->iphash[recent_entry_hash(addr)]);
130 list_add_tail(&e->lru_list, &t->lru_list);
131 t->entries++;
132 return e;
133}
1da177e4 134
404bdbfd
PM
135static void recent_entry_update(struct recent_table *t, struct recent_entry *e)
136{
137 e->stamps[e->index++] = jiffies;
138 if (e->index > e->nstamps)
139 e->nstamps = e->index;
140 e->index %= ip_pkt_list_tot;
141 list_move_tail(&e->lru_list, &t->lru_list);
142}
1da177e4 143
404bdbfd
PM
144static struct recent_table *recent_table_lookup(const char *name)
145{
146 struct recent_table *t;
1da177e4 147
404bdbfd
PM
148 list_for_each_entry(t, &tables, list)
149 if (!strcmp(t->name, name))
150 return t;
151 return NULL;
152}
1da177e4 153
404bdbfd
PM
154static void recent_table_flush(struct recent_table *t)
155{
156 struct recent_entry *e, *next;
157 unsigned int i;
1da177e4 158
404bdbfd
PM
159 for (i = 0; i < ip_list_hash_size; i++) {
160 list_for_each_entry_safe(e, next, &t->iphash[i], list)
161 recent_entry_remove(t, e);
1da177e4 162 }
1da177e4
LT
163}
164
1da177e4 165static int
404bdbfd
PM
166ipt_recent_match(const struct sk_buff *skb,
167 const struct net_device *in, const struct net_device *out,
168 const struct xt_match *match, const void *matchinfo,
169 int offset, unsigned int protoff, int *hotdrop)
1da177e4 170{
1da177e4 171 const struct ipt_recent_info *info = matchinfo;
404bdbfd
PM
172 struct recent_table *t;
173 struct recent_entry *e;
174 u_int32_t addr;
175 u_int8_t ttl;
176 int ret = info->invert;
1da177e4 177
404bdbfd
PM
178 if (info->side == IPT_RECENT_DEST)
179 addr = skb->nh.iph->daddr;
180 else
181 addr = skb->nh.iph->saddr;
1da177e4 182
404bdbfd
PM
183 ttl = skb->nh.iph->ttl;
184 /* use TTL as seen before forwarding */
185 if (out && !skb->sk)
186 ttl++;
1da177e4 187
1da177e4 188 spin_lock_bh(&recent_lock);
404bdbfd
PM
189 t = recent_table_lookup(info->name);
190 e = recent_entry_lookup(t, addr,
191 info->check_set & IPT_RECENT_TTL ? ttl : 0);
192 if (e == NULL) {
193 if (!(info->check_set & IPT_RECENT_SET))
194 goto out;
195 e = recent_entry_init(t, addr, ttl);
196 if (e == NULL)
197 *hotdrop = 1;
198 ret ^= 1;
199 goto out;
1da177e4
LT
200 }
201
404bdbfd
PM
202 if (info->check_set & IPT_RECENT_SET)
203 ret ^= 1;
204 else if (info->check_set & IPT_RECENT_REMOVE) {
205 recent_entry_remove(t, e);
206 ret ^= 1;
207 } else if (info->check_set & (IPT_RECENT_CHECK | IPT_RECENT_UPDATE)) {
208 unsigned long t = jiffies - info->seconds * HZ;
209 unsigned int i, hits = 0;
210
211 for (i = 0; i < e->nstamps; i++) {
212 if (info->seconds && time_after(t, e->stamps[i]))
213 continue;
214 if (++hits >= info->hit_count) {
215 ret ^= 1;
216 break;
1da177e4 217 }
1da177e4 218 }
1da177e4
LT
219 }
220
404bdbfd
PM
221 if (info->check_set & IPT_RECENT_SET ||
222 (info->check_set & IPT_RECENT_UPDATE && ret)) {
223 recent_entry_update(t, e);
224 e->ttl = ttl;
225 }
226out:
227 spin_unlock_bh(&recent_lock);
228 return ret;
1da177e4
LT
229}
230
1da177e4 231static int
404bdbfd
PM
232ipt_recent_checkentry(const char *tablename, const void *ip,
233 const struct xt_match *match, void *matchinfo,
234 unsigned int matchsize, unsigned int hook_mask)
1da177e4 235{
1da177e4 236 const struct ipt_recent_info *info = matchinfo;
404bdbfd
PM
237 struct recent_table *t;
238 unsigned i;
239 int ret = 0;
1da177e4 240
404bdbfd
PM
241 if (hweight8(info->check_set &
242 (IPT_RECENT_SET | IPT_RECENT_REMOVE |
243 IPT_RECENT_CHECK | IPT_RECENT_UPDATE)) != 1)
244 return 0;
245 if ((info->check_set & (IPT_RECENT_SET | IPT_RECENT_REMOVE)) &&
246 (info->seconds || info->hit_count))
247 return 0;
248 if (info->name[0] == '\0' ||
249 strnlen(info->name, IPT_RECENT_NAME_LEN) == IPT_RECENT_NAME_LEN)
250 return 0;
1da177e4 251
1da177e4 252 spin_lock_bh(&recent_lock);
404bdbfd
PM
253 t = recent_table_lookup(info->name);
254 if (t != NULL) {
255 t->refcnt++;
256 ret = 1;
257 goto out;
1da177e4
LT
258 }
259
404bdbfd
PM
260 t = kzalloc(sizeof(*t) + sizeof(t->iphash[0]) * ip_list_hash_size,
261 GFP_ATOMIC);
262 if (t == NULL)
263 goto out;
264 strcpy(t->name, info->name);
265 INIT_LIST_HEAD(&t->lru_list);
266 for (i = 0; i < ip_list_hash_size; i++)
267 INIT_LIST_HEAD(&t->iphash[i]);
268#ifdef CONFIG_PROC_FS
269 t->proc = create_proc_entry(t->name, ip_list_perms, proc_dir);
270 if (t->proc == NULL) {
271 kfree(t);
272 goto out;
1da177e4 273 }
404bdbfd
PM
274 t->proc->proc_fops = &recent_fops;
275 t->proc->data = t;
1da177e4 276#endif
404bdbfd
PM
277 list_add_tail(&t->list, &tables);
278 ret = 1;
279out:
280 spin_unlock_bh(&recent_lock);
281 return ret;
282}
1da177e4 283
404bdbfd
PM
284static void
285ipt_recent_destroy(const struct xt_match *match, void *matchinfo,
286 unsigned int matchsize)
287{
288 const struct ipt_recent_info *info = matchinfo;
289 struct recent_table *t;
1da177e4 290
404bdbfd
PM
291 spin_lock_bh(&recent_lock);
292 t = recent_table_lookup(info->name);
293 if (--t->refcnt == 0) {
294 list_del(&t->list);
295 recent_table_flush(t);
296#ifdef CONFIG_PROC_FS
297 remove_proc_entry(t->name, proc_dir);
1da177e4 298#endif
404bdbfd 299 kfree(t);
1da177e4 300 }
404bdbfd
PM
301 spin_unlock_bh(&recent_lock);
302}
1da177e4 303
404bdbfd
PM
304#ifdef CONFIG_PROC_FS
305struct recent_iter_state {
306 struct recent_table *table;
307 unsigned int bucket;
308};
1da177e4 309
404bdbfd
PM
310static void *recent_seq_start(struct seq_file *seq, loff_t *pos)
311{
312 struct recent_iter_state *st = seq->private;
313 struct recent_table *t = st->table;
314 struct recent_entry *e;
315 loff_t p = *pos;
1da177e4 316
1da177e4 317 spin_lock_bh(&recent_lock);
1da177e4 318
404bdbfd
PM
319 for (st->bucket = 0; st->bucket < ip_list_hash_size; st->bucket++) {
320 list_for_each_entry(e, &t->iphash[st->bucket], list) {
321 if (p-- == 0)
322 return e;
1da177e4 323 }
1da177e4 324 }
404bdbfd
PM
325 return NULL;
326}
1da177e4 327
404bdbfd
PM
328static void *recent_seq_next(struct seq_file *seq, void *v, loff_t *pos)
329{
330 struct recent_iter_state *st = seq->private;
331 struct recent_table *t = st->table;
332 struct recent_entry *e = v;
333 struct list_head *head = e->list.next;
334
335 while (head == &t->iphash[st->bucket]) {
336 if (++st->bucket >= ip_list_hash_size)
337 return NULL;
338 head = t->iphash[st->bucket].next;
339 }
340 (*pos)++;
341 return list_entry(head, struct recent_entry, list);
1da177e4
LT
342}
343
404bdbfd 344static void recent_seq_stop(struct seq_file *s, void *v)
1da177e4 345{
404bdbfd
PM
346 spin_unlock_bh(&recent_lock);
347}
1da177e4 348
404bdbfd
PM
349static int recent_seq_show(struct seq_file *seq, void *v)
350{
351 struct recent_entry *e = v;
352 unsigned int i;
353
354 i = (e->index - 1) % ip_pkt_list_tot;
355 seq_printf(seq, "src=%u.%u.%u.%u ttl: %u last_seen: %lu oldest_pkt: %u",
356 NIPQUAD(e->addr), e->ttl, e->stamps[i], e->index);
357 for (i = 0; i < e->nstamps; i++)
358 seq_printf(seq, "%s %lu", i ? "," : "", e->stamps[i]);
359 seq_printf(seq, "\n");
360 return 0;
361}
1da177e4 362
404bdbfd
PM
363static struct seq_operations recent_seq_ops = {
364 .start = recent_seq_start,
365 .next = recent_seq_next,
366 .stop = recent_seq_stop,
367 .show = recent_seq_show,
368};
1da177e4 369
404bdbfd
PM
370static int recent_seq_open(struct inode *inode, struct file *file)
371{
372 struct proc_dir_entry *pde = PDE(inode);
373 struct seq_file *seq;
374 struct recent_iter_state *st;
375 int ret;
376
377 st = kzalloc(sizeof(*st), GFP_KERNEL);
378 if (st == NULL)
379 return -ENOMEM;
380 ret = seq_open(file, &recent_seq_ops);
381 if (ret)
382 kfree(st);
383 st->table = pde->data;
384 seq = file->private_data;
385 seq->private = st;
386 return ret;
387}
1da177e4 388
404bdbfd
PM
389static ssize_t recent_proc_write(struct file *file, const char __user *input,
390 size_t size, loff_t *loff)
391{
392 struct proc_dir_entry *pde = PDE(file->f_dentry->d_inode);
393 struct recent_table *t = pde->data;
394 struct recent_entry *e;
395 char buf[sizeof("+255.255.255.255")], *c = buf;
396 u_int32_t addr;
397 int add;
398
399 if (size > sizeof(buf))
400 size = sizeof(buf);
401 if (copy_from_user(buf, input, size))
402 return -EFAULT;
403 while (isspace(*c))
404 c++;
405
406 if (size - (c - buf) < 5)
407 return c - buf;
408 if (!strncmp(c, "clear", 5)) {
409 c += 5;
410 spin_lock_bh(&recent_lock);
411 recent_table_flush(t);
1da177e4 412 spin_unlock_bh(&recent_lock);
404bdbfd 413 return c - buf;
1da177e4 414 }
1da177e4 415
404bdbfd
PM
416 switch (*c) {
417 case '-':
418 add = 0;
419 c++;
420 break;
421 case '+':
422 c++;
423 default:
424 add = 1;
425 break;
1da177e4 426 }
404bdbfd 427 addr = in_aton(c);
1da177e4 428
404bdbfd
PM
429 spin_lock_bh(&recent_lock);
430 e = recent_entry_lookup(t, addr, 0);
431 if (e == NULL) {
432 if (add)
433 recent_entry_init(t, addr, 0);
434 } else {
435 if (add)
436 recent_entry_update(t, e);
437 else
438 recent_entry_remove(t, e);
1da177e4 439 }
1da177e4 440 spin_unlock_bh(&recent_lock);
404bdbfd
PM
441 return size;
442}
1da177e4 443
404bdbfd
PM
444static struct file_operations recent_fops = {
445 .open = recent_seq_open,
446 .read = seq_read,
447 .write = recent_proc_write,
448 .release = seq_release_private,
449 .owner = THIS_MODULE,
450};
1da177e4 451#endif /* CONFIG_PROC_FS */
1da177e4 452
1d5cd909
PM
453static struct ipt_match recent_match = {
454 .name = "recent",
404bdbfd 455 .match = ipt_recent_match,
1d5cd909 456 .matchsize = sizeof(struct ipt_recent_info),
404bdbfd
PM
457 .checkentry = ipt_recent_checkentry,
458 .destroy = ipt_recent_destroy,
459 .me = THIS_MODULE,
1da177e4
LT
460};
461
65b4b4e8 462static int __init ipt_recent_init(void)
1da177e4 463{
404bdbfd 464 int err;
1da177e4 465
404bdbfd
PM
466 if (!ip_list_tot || !ip_pkt_list_tot || ip_pkt_list_tot > 255)
467 return -EINVAL;
468 ip_list_hash_size = 1 << fls(ip_list_tot);
1da177e4
LT
469
470 err = ipt_register_match(&recent_match);
404bdbfd 471#ifdef CONFIG_PROC_FS
1da177e4 472 if (err)
404bdbfd
PM
473 return err;
474 proc_dir = proc_mkdir("ipt_recent", proc_net);
475 if (proc_dir == NULL) {
476 ipt_unregister_match(&recent_match);
477 err = -ENOMEM;
478 }
479#endif
1da177e4
LT
480 return err;
481}
482
404bdbfd 483static void __exit ipt_recent_exit(void)
1da177e4 484{
404bdbfd 485 BUG_ON(!list_empty(&tables));
1da177e4 486 ipt_unregister_match(&recent_match);
404bdbfd
PM
487#ifdef CONFIG_PROC_FS
488 remove_proc_entry("ipt_recent", proc_net);
489#endif
1da177e4
LT
490}
491
65b4b4e8 492module_init(ipt_recent_init);
404bdbfd 493module_exit(ipt_recent_exit);
This page took 0.372994 seconds and 5 git commands to generate.