remarkable-linux/net/netfilter/xt_recent.c
Jan Engelhardt f7108a20de netfilter: xtables: move extension arguments into compound structure (1/6)
The function signatures for Xtables extensions have grown over time.
It involves a lot of typing/replication, and also a bit of stack space
even if they are not used. Realize an NFWS2008 idea and pack them into
structs. The skb remains outside of the struct so gcc can continue to
apply its optimizations.

This patch does this for match extensions' match functions.

A few ambiguities have also been addressed. The "offset" parameter for
example has been renamed to "fragoff" (there are so many different
offsets already) and "protoff" to "thoff" (there is more than just one
protocol here, so clarify).

Signed-off-by: Jan Engelhardt <jengelh@medozas.de>
Signed-off-by: Patrick McHardy <kaber@trash.net>
2008-10-08 11:35:18 +02:00

693 lines
17 KiB
C

/*
* Copyright (c) 2006 Patrick McHardy <kaber@trash.net>
* Copyright © CC Computer Consultants GmbH, 2007 - 2008
*
* 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/ipv6.h>
#include <linux/module.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 <net/net_namespace.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter/xt_recent.h>
MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
MODULE_AUTHOR("Jan Engelhardt <jengelh@computergmbh.de>");
MODULE_DESCRIPTION("Xtables: \"recently-seen\" host matching for IPv4");
MODULE_LICENSE("GPL");
MODULE_ALIAS("ipt_recent");
MODULE_ALIAS("ip6t_recent");
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/xt_recent/* files");
MODULE_PARM_DESC(ip_list_uid,"owner of /proc/net/xt_recent/* files");
MODULE_PARM_DESC(ip_list_gid,"owning group of /proc/net/xt_recent/* files");
struct recent_entry {
struct list_head list;
struct list_head lru_list;
union nf_inet_addr addr;
u_int16_t family;
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[XT_RECENT_NAME_LEN];
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *proc_old, *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
#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
static struct proc_dir_entry *proc_old_dir;
#endif
static struct proc_dir_entry *recent_proc_dir;
static const struct file_operations recent_old_fops, recent_mt_fops;
#endif
static u_int32_t hash_rnd;
static bool hash_rnd_initted;
static unsigned int recent_entry_hash4(const union nf_inet_addr *addr)
{
if (!hash_rnd_initted) {
get_random_bytes(&hash_rnd, sizeof(hash_rnd));
hash_rnd_initted = true;
}
return jhash_1word((__force u32)addr->ip, hash_rnd) &
(ip_list_hash_size - 1);
}
static unsigned int recent_entry_hash6(const union nf_inet_addr *addr)
{
if (!hash_rnd_initted) {
get_random_bytes(&hash_rnd, sizeof(hash_rnd));
hash_rnd_initted = true;
}
return jhash2((u32 *)addr->ip6, ARRAY_SIZE(addr->ip6), hash_rnd) &
(ip_list_hash_size - 1);
}
static struct recent_entry *
recent_entry_lookup(const struct recent_table *table,
const union nf_inet_addr *addrp, u_int16_t family,
u_int8_t ttl)
{
struct recent_entry *e;
unsigned int h;
if (family == NFPROTO_IPV4)
h = recent_entry_hash4(addrp);
else
h = recent_entry_hash6(addrp);
list_for_each_entry(e, &table->iphash[h], list)
if (e->family == family &&
memcmp(&e->addr, addrp, sizeof(e->addr)) == 0 &&
(ttl == e->ttl || ttl == 0 || e->ttl == 0))
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, const union nf_inet_addr *addr,
u_int16_t family, 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;
memcpy(&e->addr, addr, sizeof(e->addr));
e->ttl = ttl;
e->stamps[0] = jiffies;
e->nstamps = 1;
e->index = 1;
e->family = family;
if (family == NFPROTO_IPV4)
list_add_tail(&e->list, &t->iphash[recent_entry_hash4(addr)]);
else
list_add_tail(&e->list, &t->iphash[recent_entry_hash6(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 bool
recent_mt(const struct sk_buff *skb, const struct xt_match_param *par)
{
const struct xt_recent_mtinfo *info = par->matchinfo;
struct recent_table *t;
struct recent_entry *e;
union nf_inet_addr addr = {};
u_int8_t ttl;
bool ret = info->invert;
if (par->match->family == NFPROTO_IPV4) {
const struct iphdr *iph = ip_hdr(skb);
if (info->side == XT_RECENT_DEST)
addr.ip = iph->daddr;
else
addr.ip = iph->saddr;
ttl = iph->ttl;
} else {
const struct ipv6hdr *iph = ipv6_hdr(skb);
if (info->side == XT_RECENT_DEST)
memcpy(&addr.in6, &iph->daddr, sizeof(addr.in6));
else
memcpy(&addr.in6, &iph->saddr, sizeof(addr.in6));
ttl = iph->hop_limit;
}
/* use TTL as seen before forwarding */
if (par->out != NULL && skb->sk == NULL)
ttl++;
spin_lock_bh(&recent_lock);
t = recent_table_lookup(info->name);
e = recent_entry_lookup(t, &addr, par->match->family,
(info->check_set & XT_RECENT_TTL) ? ttl : 0);
if (e == NULL) {
if (!(info->check_set & XT_RECENT_SET))
goto out;
e = recent_entry_init(t, &addr, par->match->family, ttl);
if (e == NULL)
*par->hotdrop = true;
ret = !ret;
goto out;
}
if (info->check_set & XT_RECENT_SET)
ret = !ret;
else if (info->check_set & XT_RECENT_REMOVE) {
recent_entry_remove(t, e);
ret = !ret;
} else if (info->check_set & (XT_RECENT_CHECK | XT_RECENT_UPDATE)) {
unsigned long time = jiffies - info->seconds * HZ;
unsigned int i, hits = 0;
for (i = 0; i < e->nstamps; i++) {
if (info->seconds && time_after(time, e->stamps[i]))
continue;
if (++hits >= info->hit_count) {
ret = !ret;
break;
}
}
}
if (info->check_set & XT_RECENT_SET ||
(info->check_set & XT_RECENT_UPDATE && ret)) {
recent_entry_update(t, e);
e->ttl = ttl;
}
out:
spin_unlock_bh(&recent_lock);
return ret;
}
static bool
recent_mt_check(const char *tablename, const void *ip,
const struct xt_match *match, void *matchinfo,
unsigned int hook_mask)
{
const struct xt_recent_mtinfo *info = matchinfo;
struct recent_table *t;
unsigned i;
bool ret = false;
if (hweight8(info->check_set &
(XT_RECENT_SET | XT_RECENT_REMOVE |
XT_RECENT_CHECK | XT_RECENT_UPDATE)) != 1)
return false;
if ((info->check_set & (XT_RECENT_SET | XT_RECENT_REMOVE)) &&
(info->seconds || info->hit_count))
return false;
if (info->hit_count > ip_pkt_list_tot)
return false;
if (info->name[0] == '\0' ||
strnlen(info->name, XT_RECENT_NAME_LEN) == XT_RECENT_NAME_LEN)
return false;
mutex_lock(&recent_mutex);
t = recent_table_lookup(info->name);
if (t != NULL) {
t->refcnt++;
ret = true;
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 = proc_create(t->name, ip_list_perms, recent_proc_dir,
&recent_mt_fops);
if (t->proc == NULL) {
kfree(t);
goto out;
}
#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
t->proc_old = proc_create(t->name, ip_list_perms, proc_old_dir,
&recent_old_fops);
if (t->proc_old == NULL) {
remove_proc_entry(t->name, proc_old_dir);
kfree(t);
goto out;
}
t->proc_old->uid = ip_list_uid;
t->proc_old->gid = ip_list_gid;
t->proc_old->data = t;
#endif
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 = true;
out:
mutex_unlock(&recent_mutex);
return ret;
}
static void recent_mt_destroy(const struct xt_match *match, void *matchinfo)
{
const struct xt_recent_mtinfo *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);
#ifdef CONFIG_PROC_FS
#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
remove_proc_entry(t->name, proc_old_dir);
#endif
remove_proc_entry(t->name, recent_proc_dir);
#endif
recent_table_flush(t);
kfree(t);
}
mutex_unlock(&recent_mutex);
}
#ifdef CONFIG_PROC_FS
struct recent_iter_state {
const struct recent_table *table;
unsigned int bucket;
};
static void *recent_seq_start(struct seq_file *seq, loff_t *pos)
__acquires(recent_lock)
{
struct recent_iter_state *st = seq->private;
const 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;
const struct recent_table *t = st->table;
const struct recent_entry *e = v;
const 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)
__releases(recent_lock)
{
spin_unlock_bh(&recent_lock);
}
static int recent_seq_show(struct seq_file *seq, void *v)
{
const struct recent_entry *e = v;
unsigned int i;
i = (e->index - 1) % ip_pkt_list_tot;
if (e->family == NFPROTO_IPV4)
seq_printf(seq, "src=" NIPQUAD_FMT " ttl: %u last_seen: %lu "
"oldest_pkt: %u", NIPQUAD(e->addr.ip), e->ttl,
e->stamps[i], e->index);
else
seq_printf(seq, "src=" NIP6_FMT " ttl: %u last_seen: %lu "
"oldest_pkt: %u", NIP6(e->addr.in6), 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 const 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 recent_iter_state *st;
st = __seq_open_private(file, &recent_seq_ops, sizeof(*st));
if (st == NULL)
return -ENOMEM;
st->table = pde->data;
return 0;
}
#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
static int recent_old_seq_open(struct inode *inode, struct file *filp)
{
static bool warned_of_old;
if (unlikely(!warned_of_old)) {
printk(KERN_INFO KBUILD_MODNAME ": Use of /proc/net/ipt_recent"
" is deprecated; use /proc/net/xt_recent.\n");
warned_of_old = true;
}
return recent_seq_open(inode, filp);
}
static ssize_t recent_old_proc_write(struct file *file,
const char __user *input,
size_t size, loff_t *loff)
{
const 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, (const void *)&addr, NFPROTO_IPV4, 0);
if (e == NULL) {
if (add)
recent_entry_init(t, (const void *)&addr,
NFPROTO_IPV4, 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_old_fops = {
.open = recent_old_seq_open,
.read = seq_read,
.write = recent_old_proc_write,
.release = seq_release_private,
.owner = THIS_MODULE,
};
#endif
static ssize_t
recent_mt_proc_write(struct file *file, const char __user *input,
size_t size, loff_t *loff)
{
const 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("+b335:1d35:1e55:dead:c0de:1715:5afe:c0de")];
const char *c = buf;
union nf_inet_addr addr;
u_int16_t family;
bool add, succ;
if (size == 0)
return 0;
if (size > sizeof(buf))
size = sizeof(buf);
if (copy_from_user(buf, input, size) != 0)
return -EFAULT;
/* Strict protocol! */
if (*loff != 0)
return -ESPIPE;
switch (*c) {
case '/': /* flush table */
spin_lock_bh(&recent_lock);
recent_table_flush(t);
spin_unlock_bh(&recent_lock);
return size;
case '-': /* remove address */
add = false;
break;
case '+': /* add address */
add = true;
break;
default:
printk(KERN_INFO KBUILD_MODNAME ": Need +ip, -ip or /\n");
return -EINVAL;
}
++c;
--size;
if (strnchr(c, size, ':') != NULL) {
family = NFPROTO_IPV6;
succ = in6_pton(c, size, (void *)&addr, '\n', NULL);
} else {
family = NFPROTO_IPV4;
succ = in4_pton(c, size, (void *)&addr, '\n', NULL);
}
if (!succ) {
printk(KERN_INFO KBUILD_MODNAME ": illegal address written "
"to procfs\n");
return -EINVAL;
}
spin_lock_bh(&recent_lock);
e = recent_entry_lookup(t, &addr, family, 0);
if (e == NULL) {
if (add)
recent_entry_init(t, &addr, family, 0);
} else {
if (add)
recent_entry_update(t, e);
else
recent_entry_remove(t, e);
}
spin_unlock_bh(&recent_lock);
/* Note we removed one above */
*loff += size + 1;
return size + 1;
}
static const struct file_operations recent_mt_fops = {
.open = recent_seq_open,
.read = seq_read,
.write = recent_mt_proc_write,
.release = seq_release_private,
.owner = THIS_MODULE,
};
#endif /* CONFIG_PROC_FS */
static struct xt_match recent_mt_reg[] __read_mostly = {
{
.name = "recent",
.revision = 0,
.family = NFPROTO_IPV4,
.match = recent_mt,
.matchsize = sizeof(struct xt_recent_mtinfo),
.checkentry = recent_mt_check,
.destroy = recent_mt_destroy,
.me = THIS_MODULE,
},
{
.name = "recent",
.revision = 0,
.family = NFPROTO_IPV6,
.match = recent_mt,
.matchsize = sizeof(struct xt_recent_mtinfo),
.checkentry = recent_mt_check,
.destroy = recent_mt_destroy,
.me = THIS_MODULE,
},
};
static int __init recent_mt_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_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
#ifdef CONFIG_PROC_FS
if (err)
return err;
recent_proc_dir = proc_mkdir("xt_recent", init_net.proc_net);
if (recent_proc_dir == NULL) {
xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
err = -ENOMEM;
}
#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
if (err < 0)
return err;
proc_old_dir = proc_mkdir("ipt_recent", init_net.proc_net);
if (proc_old_dir == NULL) {
remove_proc_entry("xt_recent", init_net.proc_net);
xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
err = -ENOMEM;
}
#endif
#endif
return err;
}
static void __exit recent_mt_exit(void)
{
BUG_ON(!list_empty(&tables));
xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
#ifdef CONFIG_PROC_FS
#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
remove_proc_entry("ipt_recent", init_net.proc_net);
#endif
remove_proc_entry("xt_recent", init_net.proc_net);
#endif
}
module_init(recent_mt_init);
module_exit(recent_mt_exit);