Many struct file_operations in the kernel can be "const". Marking them const moves these to the .rodata section, which avoids false sharing with potential dirty data. In addition it'll catch accidental writes at compile time to these shared resources. Signed-off-by: Arjan van de Ven <arjan@linux.intel.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
508 lines
12 KiB
C
508 lines
12 KiB
C
/*
|
|
* Copyright (c) 2006 Patrick McHardy <kaber@trash.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This is a replacement of the old ipt_recent module, which carried the
|
|
* following copyright notice:
|
|
*
|
|
* Author: Stephen Frost <sfrost@snowman.net>
|
|
* Copyright 2002-2003, Stephen Frost, 2.5.x port by laforge@netfilter.org
|
|
*/
|
|
#include <linux/init.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/string.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/list.h>
|
|
#include <linux/random.h>
|
|
#include <linux/jhash.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/inet.h>
|
|
|
|
#include <linux/netfilter/x_tables.h>
|
|
#include <linux/netfilter_ipv4/ipt_recent.h>
|
|
|
|
MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
|
|
MODULE_DESCRIPTION("IP tables recently seen matching module");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
static unsigned int ip_list_tot = 100;
|
|
static unsigned int ip_pkt_list_tot = 20;
|
|
static unsigned int ip_list_hash_size = 0;
|
|
static unsigned int ip_list_perms = 0644;
|
|
static unsigned int ip_list_uid = 0;
|
|
static unsigned int ip_list_gid = 0;
|
|
module_param(ip_list_tot, uint, 0400);
|
|
module_param(ip_pkt_list_tot, uint, 0400);
|
|
module_param(ip_list_hash_size, uint, 0400);
|
|
module_param(ip_list_perms, uint, 0400);
|
|
module_param(ip_list_uid, uint, 0400);
|
|
module_param(ip_list_gid, uint, 0400);
|
|
MODULE_PARM_DESC(ip_list_tot, "number of IPs to remember per list");
|
|
MODULE_PARM_DESC(ip_pkt_list_tot, "number of packets per IP to remember (max. 255)");
|
|
MODULE_PARM_DESC(ip_list_hash_size, "size of hash table used to look up IPs");
|
|
MODULE_PARM_DESC(ip_list_perms, "permissions on /proc/net/ipt_recent/* files");
|
|
MODULE_PARM_DESC(ip_list_uid,"owner of /proc/net/ipt_recent/* files");
|
|
MODULE_PARM_DESC(ip_list_gid,"owning group of /proc/net/ipt_recent/* files");
|
|
|
|
struct recent_entry {
|
|
struct list_head list;
|
|
struct list_head lru_list;
|
|
__be32 addr;
|
|
u_int8_t ttl;
|
|
u_int8_t index;
|
|
u_int16_t nstamps;
|
|
unsigned long stamps[0];
|
|
};
|
|
|
|
struct recent_table {
|
|
struct list_head list;
|
|
char name[IPT_RECENT_NAME_LEN];
|
|
#ifdef CONFIG_PROC_FS
|
|
struct proc_dir_entry *proc;
|
|
#endif
|
|
unsigned int refcnt;
|
|
unsigned int entries;
|
|
struct list_head lru_list;
|
|
struct list_head iphash[0];
|
|
};
|
|
|
|
static LIST_HEAD(tables);
|
|
static DEFINE_SPINLOCK(recent_lock);
|
|
static DEFINE_MUTEX(recent_mutex);
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static struct proc_dir_entry *proc_dir;
|
|
static const struct file_operations recent_fops;
|
|
#endif
|
|
|
|
static u_int32_t hash_rnd;
|
|
static int hash_rnd_initted;
|
|
|
|
static unsigned int recent_entry_hash(__be32 addr)
|
|
{
|
|
if (!hash_rnd_initted) {
|
|
get_random_bytes(&hash_rnd, 4);
|
|
hash_rnd_initted = 1;
|
|
}
|
|
return jhash_1word((__force u32)addr, hash_rnd) & (ip_list_hash_size - 1);
|
|
}
|
|
|
|
static struct recent_entry *
|
|
recent_entry_lookup(const struct recent_table *table, __be32 addr, u_int8_t ttl)
|
|
{
|
|
struct recent_entry *e;
|
|
unsigned int h;
|
|
|
|
h = recent_entry_hash(addr);
|
|
list_for_each_entry(e, &table->iphash[h], list)
|
|
if (e->addr == addr && (ttl == e->ttl || !ttl || !e->ttl))
|
|
return e;
|
|
return NULL;
|
|
}
|
|
|
|
static void recent_entry_remove(struct recent_table *t, struct recent_entry *e)
|
|
{
|
|
list_del(&e->list);
|
|
list_del(&e->lru_list);
|
|
kfree(e);
|
|
t->entries--;
|
|
}
|
|
|
|
static struct recent_entry *
|
|
recent_entry_init(struct recent_table *t, __be32 addr, u_int8_t ttl)
|
|
{
|
|
struct recent_entry *e;
|
|
|
|
if (t->entries >= ip_list_tot) {
|
|
e = list_entry(t->lru_list.next, struct recent_entry, lru_list);
|
|
recent_entry_remove(t, e);
|
|
}
|
|
e = kmalloc(sizeof(*e) + sizeof(e->stamps[0]) * ip_pkt_list_tot,
|
|
GFP_ATOMIC);
|
|
if (e == NULL)
|
|
return NULL;
|
|
e->addr = addr;
|
|
e->ttl = ttl;
|
|
e->stamps[0] = jiffies;
|
|
e->nstamps = 1;
|
|
e->index = 1;
|
|
list_add_tail(&e->list, &t->iphash[recent_entry_hash(addr)]);
|
|
list_add_tail(&e->lru_list, &t->lru_list);
|
|
t->entries++;
|
|
return e;
|
|
}
|
|
|
|
static void recent_entry_update(struct recent_table *t, struct recent_entry *e)
|
|
{
|
|
e->stamps[e->index++] = jiffies;
|
|
if (e->index > e->nstamps)
|
|
e->nstamps = e->index;
|
|
e->index %= ip_pkt_list_tot;
|
|
list_move_tail(&e->lru_list, &t->lru_list);
|
|
}
|
|
|
|
static struct recent_table *recent_table_lookup(const char *name)
|
|
{
|
|
struct recent_table *t;
|
|
|
|
list_for_each_entry(t, &tables, list)
|
|
if (!strcmp(t->name, name))
|
|
return t;
|
|
return NULL;
|
|
}
|
|
|
|
static void recent_table_flush(struct recent_table *t)
|
|
{
|
|
struct recent_entry *e, *next;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ip_list_hash_size; i++) {
|
|
list_for_each_entry_safe(e, next, &t->iphash[i], list)
|
|
recent_entry_remove(t, e);
|
|
}
|
|
}
|
|
|
|
static int
|
|
ipt_recent_match(const struct sk_buff *skb,
|
|
const struct net_device *in, const struct net_device *out,
|
|
const struct xt_match *match, const void *matchinfo,
|
|
int offset, unsigned int protoff, int *hotdrop)
|
|
{
|
|
const struct ipt_recent_info *info = matchinfo;
|
|
struct recent_table *t;
|
|
struct recent_entry *e;
|
|
__be32 addr;
|
|
u_int8_t ttl;
|
|
int ret = info->invert;
|
|
|
|
if (info->side == IPT_RECENT_DEST)
|
|
addr = skb->nh.iph->daddr;
|
|
else
|
|
addr = skb->nh.iph->saddr;
|
|
|
|
ttl = skb->nh.iph->ttl;
|
|
/* use TTL as seen before forwarding */
|
|
if (out && !skb->sk)
|
|
ttl++;
|
|
|
|
spin_lock_bh(&recent_lock);
|
|
t = recent_table_lookup(info->name);
|
|
e = recent_entry_lookup(t, addr,
|
|
info->check_set & IPT_RECENT_TTL ? ttl : 0);
|
|
if (e == NULL) {
|
|
if (!(info->check_set & IPT_RECENT_SET))
|
|
goto out;
|
|
e = recent_entry_init(t, addr, ttl);
|
|
if (e == NULL)
|
|
*hotdrop = 1;
|
|
ret ^= 1;
|
|
goto out;
|
|
}
|
|
|
|
if (info->check_set & IPT_RECENT_SET)
|
|
ret ^= 1;
|
|
else if (info->check_set & IPT_RECENT_REMOVE) {
|
|
recent_entry_remove(t, e);
|
|
ret ^= 1;
|
|
} else if (info->check_set & (IPT_RECENT_CHECK | IPT_RECENT_UPDATE)) {
|
|
unsigned long t = jiffies - info->seconds * HZ;
|
|
unsigned int i, hits = 0;
|
|
|
|
for (i = 0; i < e->nstamps; i++) {
|
|
if (info->seconds && time_after(t, e->stamps[i]))
|
|
continue;
|
|
if (++hits >= info->hit_count) {
|
|
ret ^= 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (info->check_set & IPT_RECENT_SET ||
|
|
(info->check_set & IPT_RECENT_UPDATE && ret)) {
|
|
recent_entry_update(t, e);
|
|
e->ttl = ttl;
|
|
}
|
|
out:
|
|
spin_unlock_bh(&recent_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
ipt_recent_checkentry(const char *tablename, const void *ip,
|
|
const struct xt_match *match, void *matchinfo,
|
|
unsigned int hook_mask)
|
|
{
|
|
const struct ipt_recent_info *info = matchinfo;
|
|
struct recent_table *t;
|
|
unsigned i;
|
|
int ret = 0;
|
|
|
|
if (hweight8(info->check_set &
|
|
(IPT_RECENT_SET | IPT_RECENT_REMOVE |
|
|
IPT_RECENT_CHECK | IPT_RECENT_UPDATE)) != 1)
|
|
return 0;
|
|
if ((info->check_set & (IPT_RECENT_SET | IPT_RECENT_REMOVE)) &&
|
|
(info->seconds || info->hit_count))
|
|
return 0;
|
|
if (info->name[0] == '\0' ||
|
|
strnlen(info->name, IPT_RECENT_NAME_LEN) == IPT_RECENT_NAME_LEN)
|
|
return 0;
|
|
|
|
mutex_lock(&recent_mutex);
|
|
t = recent_table_lookup(info->name);
|
|
if (t != NULL) {
|
|
t->refcnt++;
|
|
ret = 1;
|
|
goto out;
|
|
}
|
|
|
|
t = kzalloc(sizeof(*t) + sizeof(t->iphash[0]) * ip_list_hash_size,
|
|
GFP_KERNEL);
|
|
if (t == NULL)
|
|
goto out;
|
|
t->refcnt = 1;
|
|
strcpy(t->name, info->name);
|
|
INIT_LIST_HEAD(&t->lru_list);
|
|
for (i = 0; i < ip_list_hash_size; i++)
|
|
INIT_LIST_HEAD(&t->iphash[i]);
|
|
#ifdef CONFIG_PROC_FS
|
|
t->proc = create_proc_entry(t->name, ip_list_perms, proc_dir);
|
|
if (t->proc == NULL) {
|
|
kfree(t);
|
|
goto out;
|
|
}
|
|
t->proc->proc_fops = &recent_fops;
|
|
t->proc->uid = ip_list_uid;
|
|
t->proc->gid = ip_list_gid;
|
|
t->proc->data = t;
|
|
#endif
|
|
spin_lock_bh(&recent_lock);
|
|
list_add_tail(&t->list, &tables);
|
|
spin_unlock_bh(&recent_lock);
|
|
ret = 1;
|
|
out:
|
|
mutex_unlock(&recent_mutex);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
ipt_recent_destroy(const struct xt_match *match, void *matchinfo)
|
|
{
|
|
const struct ipt_recent_info *info = matchinfo;
|
|
struct recent_table *t;
|
|
|
|
mutex_lock(&recent_mutex);
|
|
t = recent_table_lookup(info->name);
|
|
if (--t->refcnt == 0) {
|
|
spin_lock_bh(&recent_lock);
|
|
list_del(&t->list);
|
|
spin_unlock_bh(&recent_lock);
|
|
recent_table_flush(t);
|
|
#ifdef CONFIG_PROC_FS
|
|
remove_proc_entry(t->name, proc_dir);
|
|
#endif
|
|
kfree(t);
|
|
}
|
|
mutex_unlock(&recent_mutex);
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
struct recent_iter_state {
|
|
struct recent_table *table;
|
|
unsigned int bucket;
|
|
};
|
|
|
|
static void *recent_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
struct recent_iter_state *st = seq->private;
|
|
struct recent_table *t = st->table;
|
|
struct recent_entry *e;
|
|
loff_t p = *pos;
|
|
|
|
spin_lock_bh(&recent_lock);
|
|
|
|
for (st->bucket = 0; st->bucket < ip_list_hash_size; st->bucket++) {
|
|
list_for_each_entry(e, &t->iphash[st->bucket], list) {
|
|
if (p-- == 0)
|
|
return e;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void *recent_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
struct recent_iter_state *st = seq->private;
|
|
struct recent_table *t = st->table;
|
|
struct recent_entry *e = v;
|
|
struct list_head *head = e->list.next;
|
|
|
|
while (head == &t->iphash[st->bucket]) {
|
|
if (++st->bucket >= ip_list_hash_size)
|
|
return NULL;
|
|
head = t->iphash[st->bucket].next;
|
|
}
|
|
(*pos)++;
|
|
return list_entry(head, struct recent_entry, list);
|
|
}
|
|
|
|
static void recent_seq_stop(struct seq_file *s, void *v)
|
|
{
|
|
spin_unlock_bh(&recent_lock);
|
|
}
|
|
|
|
static int recent_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct recent_entry *e = v;
|
|
unsigned int i;
|
|
|
|
i = (e->index - 1) % ip_pkt_list_tot;
|
|
seq_printf(seq, "src=%u.%u.%u.%u ttl: %u last_seen: %lu oldest_pkt: %u",
|
|
NIPQUAD(e->addr), e->ttl, e->stamps[i], e->index);
|
|
for (i = 0; i < e->nstamps; i++)
|
|
seq_printf(seq, "%s %lu", i ? "," : "", e->stamps[i]);
|
|
seq_printf(seq, "\n");
|
|
return 0;
|
|
}
|
|
|
|
static struct seq_operations recent_seq_ops = {
|
|
.start = recent_seq_start,
|
|
.next = recent_seq_next,
|
|
.stop = recent_seq_stop,
|
|
.show = recent_seq_show,
|
|
};
|
|
|
|
static int recent_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct proc_dir_entry *pde = PDE(inode);
|
|
struct seq_file *seq;
|
|
struct recent_iter_state *st;
|
|
int ret;
|
|
|
|
st = kzalloc(sizeof(*st), GFP_KERNEL);
|
|
if (st == NULL)
|
|
return -ENOMEM;
|
|
ret = seq_open(file, &recent_seq_ops);
|
|
if (ret)
|
|
kfree(st);
|
|
st->table = pde->data;
|
|
seq = file->private_data;
|
|
seq->private = st;
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t recent_proc_write(struct file *file, const char __user *input,
|
|
size_t size, loff_t *loff)
|
|
{
|
|
struct proc_dir_entry *pde = PDE(file->f_path.dentry->d_inode);
|
|
struct recent_table *t = pde->data;
|
|
struct recent_entry *e;
|
|
char buf[sizeof("+255.255.255.255")], *c = buf;
|
|
__be32 addr;
|
|
int add;
|
|
|
|
if (size > sizeof(buf))
|
|
size = sizeof(buf);
|
|
if (copy_from_user(buf, input, size))
|
|
return -EFAULT;
|
|
while (isspace(*c))
|
|
c++;
|
|
|
|
if (size - (c - buf) < 5)
|
|
return c - buf;
|
|
if (!strncmp(c, "clear", 5)) {
|
|
c += 5;
|
|
spin_lock_bh(&recent_lock);
|
|
recent_table_flush(t);
|
|
spin_unlock_bh(&recent_lock);
|
|
return c - buf;
|
|
}
|
|
|
|
switch (*c) {
|
|
case '-':
|
|
add = 0;
|
|
c++;
|
|
break;
|
|
case '+':
|
|
c++;
|
|
default:
|
|
add = 1;
|
|
break;
|
|
}
|
|
addr = in_aton(c);
|
|
|
|
spin_lock_bh(&recent_lock);
|
|
e = recent_entry_lookup(t, addr, 0);
|
|
if (e == NULL) {
|
|
if (add)
|
|
recent_entry_init(t, addr, 0);
|
|
} else {
|
|
if (add)
|
|
recent_entry_update(t, e);
|
|
else
|
|
recent_entry_remove(t, e);
|
|
}
|
|
spin_unlock_bh(&recent_lock);
|
|
return size;
|
|
}
|
|
|
|
static const struct file_operations recent_fops = {
|
|
.open = recent_seq_open,
|
|
.read = seq_read,
|
|
.write = recent_proc_write,
|
|
.release = seq_release_private,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
static struct xt_match recent_match = {
|
|
.name = "recent",
|
|
.family = AF_INET,
|
|
.match = ipt_recent_match,
|
|
.matchsize = sizeof(struct ipt_recent_info),
|
|
.checkentry = ipt_recent_checkentry,
|
|
.destroy = ipt_recent_destroy,
|
|
.me = THIS_MODULE,
|
|
};
|
|
|
|
static int __init ipt_recent_init(void)
|
|
{
|
|
int err;
|
|
|
|
if (!ip_list_tot || !ip_pkt_list_tot || ip_pkt_list_tot > 255)
|
|
return -EINVAL;
|
|
ip_list_hash_size = 1 << fls(ip_list_tot);
|
|
|
|
err = xt_register_match(&recent_match);
|
|
#ifdef CONFIG_PROC_FS
|
|
if (err)
|
|
return err;
|
|
proc_dir = proc_mkdir("ipt_recent", proc_net);
|
|
if (proc_dir == NULL) {
|
|
xt_unregister_match(&recent_match);
|
|
err = -ENOMEM;
|
|
}
|
|
#endif
|
|
return err;
|
|
}
|
|
|
|
static void __exit ipt_recent_exit(void)
|
|
{
|
|
BUG_ON(!list_empty(&tables));
|
|
xt_unregister_match(&recent_match);
|
|
#ifdef CONFIG_PROC_FS
|
|
remove_proc_entry("ipt_recent", proc_net);
|
|
#endif
|
|
}
|
|
|
|
module_init(ipt_recent_init);
|
|
module_exit(ipt_recent_exit);
|