Files
xtables-addons/extensions/ipset/ip_set_iptree.c
2008-11-11 19:36:33 +01:00

467 lines
11 KiB
C

/* Copyright (C) 2005-2008 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
*
* 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.
*/
/* Kernel module implementing an IP set type: the iptree type */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/ip.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include <asm/bitops.h>
#include <linux/spinlock.h>
#include <linux/timer.h>
#include "ip_set.h"
#include "ip_set_bitmaps.h"
#include "ip_set_iptree.h"
static int limit = MAX_RANGE;
/* Garbage collection interval in seconds: */
#define IPTREE_GC_TIME 5*60
/* Sleep so many milliseconds before trying again
* to delete the gc timer at destroying/flushing a set */
#define IPTREE_DESTROY_SLEEP 100
static __KMEM_CACHE_T__ *branch_cachep;
static __KMEM_CACHE_T__ *leaf_cachep;
#if defined(__LITTLE_ENDIAN)
#define ABCD(a,b,c,d,addrp) do { \
a = ((unsigned char *)addrp)[3]; \
b = ((unsigned char *)addrp)[2]; \
c = ((unsigned char *)addrp)[1]; \
d = ((unsigned char *)addrp)[0]; \
} while (0)
#elif defined(__BIG_ENDIAN)
#define ABCD(a,b,c,d,addrp) do { \
a = ((unsigned char *)addrp)[0]; \
b = ((unsigned char *)addrp)[1]; \
c = ((unsigned char *)addrp)[2]; \
d = ((unsigned char *)addrp)[3]; \
} while (0)
#else
#error "Please fix asm/byteorder.h"
#endif /* __LITTLE_ENDIAN */
#define TESTIP_WALK(map, elem, branch) do { \
if ((map)->tree[elem]) { \
branch = (map)->tree[elem]; \
} else \
return 0; \
} while (0)
static inline int
iptree_test(struct ip_set *set, ip_set_ip_t *hash_ip, ip_set_ip_t ip)
{
struct ip_set_iptree *map = set->data;
struct ip_set_iptreeb *btree;
struct ip_set_iptreec *ctree;
struct ip_set_iptreed *dtree;
unsigned char a,b,c,d;
if (!ip)
return -ERANGE;
*hash_ip = ip;
ABCD(a, b, c, d, hash_ip);
DP("%u %u %u %u timeout %u", a, b, c, d, map->timeout);
TESTIP_WALK(map, a, btree);
TESTIP_WALK(btree, b, ctree);
TESTIP_WALK(ctree, c, dtree);
DP("%lu %lu", dtree->expires[d], jiffies);
return dtree->expires[d]
&& (!map->timeout
|| time_after(dtree->expires[d], jiffies));
}
#define KADT_CONDITION
UADT(iptree, test)
KADT(iptree, test, ipaddr)
#define ADDIP_WALK(map, elem, branch, type, cachep) do { \
if ((map)->tree[elem]) { \
DP("found %u", elem); \
branch = (map)->tree[elem]; \
} else { \
branch = (type *) \
kmem_cache_alloc(cachep, GFP_ATOMIC); \
if (branch == NULL) \
return -ENOMEM; \
memset(branch, 0, sizeof(*branch)); \
(map)->tree[elem] = branch; \
DP("alloc %u", elem); \
} \
} while (0)
static inline int
iptree_add(struct ip_set *set, ip_set_ip_t *hash_ip,
ip_set_ip_t ip, unsigned int timeout)
{
struct ip_set_iptree *map = set->data;
struct ip_set_iptreeb *btree;
struct ip_set_iptreec *ctree;
struct ip_set_iptreed *dtree;
unsigned char a,b,c,d;
int ret = 0;
if (!ip || map->elements >= limit)
/* We could call the garbage collector
* but it's probably overkill */
return -ERANGE;
*hash_ip = ip;
ABCD(a, b, c, d, hash_ip);
DP("%u %u %u %u timeout %u", a, b, c, d, timeout);
ADDIP_WALK(map, a, btree, struct ip_set_iptreeb, branch_cachep);
ADDIP_WALK(btree, b, ctree, struct ip_set_iptreec, branch_cachep);
ADDIP_WALK(ctree, c, dtree, struct ip_set_iptreed, leaf_cachep);
if (dtree->expires[d]
&& (!map->timeout || time_after(dtree->expires[d], jiffies)))
ret = -EEXIST;
if (map->timeout && timeout == 0)
timeout = map->timeout;
dtree->expires[d] = map->timeout ? (timeout * HZ + jiffies) : 1;
/* Lottery: I won! */
if (dtree->expires[d] == 0)
dtree->expires[d] = 1;
DP("%u %lu", d, dtree->expires[d]);
if (ret == 0)
map->elements++;
return ret;
}
UADT(iptree, add, req->timeout)
KADT(iptree, add, ipaddr, 0)
#define DELIP_WALK(map, elem, branch) do { \
if ((map)->tree[elem]) { \
branch = (map)->tree[elem]; \
} else \
return -EEXIST; \
} while (0)
static inline int
iptree_del(struct ip_set *set, ip_set_ip_t *hash_ip, ip_set_ip_t ip)
{
struct ip_set_iptree *map = set->data;
struct ip_set_iptreeb *btree;
struct ip_set_iptreec *ctree;
struct ip_set_iptreed *dtree;
unsigned char a,b,c,d;
if (!ip)
return -ERANGE;
*hash_ip = ip;
ABCD(a, b, c, d, hash_ip);
DELIP_WALK(map, a, btree);
DELIP_WALK(btree, b, ctree);
DELIP_WALK(ctree, c, dtree);
if (dtree->expires[d]) {
dtree->expires[d] = 0;
map->elements--;
return 0;
}
return -EEXIST;
}
UADT(iptree, del)
KADT(iptree, del, ipaddr)
#define LOOP_WALK_BEGIN(map, i, branch) \
for (i = 0; i < 256; i++) { \
if (!(map)->tree[i]) \
continue; \
branch = (map)->tree[i]
#define LOOP_WALK_END }
static void
ip_tree_gc(unsigned long ul_set)
{
struct ip_set *set = (struct ip_set *) ul_set;
struct ip_set_iptree *map = set->data;
struct ip_set_iptreeb *btree;
struct ip_set_iptreec *ctree;
struct ip_set_iptreed *dtree;
unsigned int a,b,c,d;
unsigned char i,j,k;
i = j = k = 0;
DP("gc: %s", set->name);
write_lock_bh(&set->lock);
LOOP_WALK_BEGIN(map, a, btree);
LOOP_WALK_BEGIN(btree, b, ctree);
LOOP_WALK_BEGIN(ctree, c, dtree);
for (d = 0; d < 256; d++) {
if (dtree->expires[d]) {
DP("gc: %u %u %u %u: expires %lu jiffies %lu",
a, b, c, d,
dtree->expires[d], jiffies);
if (map->timeout
&& time_before(dtree->expires[d], jiffies)) {
dtree->expires[d] = 0;
map->elements--;
} else
k = 1;
}
}
if (k == 0) {
DP("gc: %s: leaf %u %u %u empty",
set->name, a, b, c);
kmem_cache_free(leaf_cachep, dtree);
ctree->tree[c] = NULL;
} else {
DP("gc: %s: leaf %u %u %u not empty",
set->name, a, b, c);
j = 1;
k = 0;
}
LOOP_WALK_END;
if (j == 0) {
DP("gc: %s: branch %u %u empty",
set->name, a, b);
kmem_cache_free(branch_cachep, ctree);
btree->tree[b] = NULL;
} else {
DP("gc: %s: branch %u %u not empty",
set->name, a, b);
i = 1;
j = k = 0;
}
LOOP_WALK_END;
if (i == 0) {
DP("gc: %s: branch %u empty",
set->name, a);
kmem_cache_free(branch_cachep, btree);
map->tree[a] = NULL;
} else {
DP("gc: %s: branch %u not empty",
set->name, a);
i = j = k = 0;
}
LOOP_WALK_END;
write_unlock_bh(&set->lock);
map->gc.expires = jiffies + map->gc_interval * HZ;
add_timer(&map->gc);
}
static inline void
init_gc_timer(struct ip_set *set)
{
struct ip_set_iptree *map = set->data;
/* Even if there is no timeout for the entries,
* we still have to call gc because delete
* do not clean up empty branches */
map->gc_interval = IPTREE_GC_TIME;
init_timer(&map->gc);
map->gc.data = (unsigned long) set;
map->gc.function = ip_tree_gc;
map->gc.expires = jiffies + map->gc_interval * HZ;
add_timer(&map->gc);
}
static int
iptree_create(struct ip_set *set, const void *data, size_t size)
{
const struct ip_set_req_iptree_create *req = data;
struct ip_set_iptree *map;
if (size != sizeof(struct ip_set_req_iptree_create)) {
ip_set_printk("data length wrong (want %zu, have %zu)",
sizeof(struct ip_set_req_iptree_create),
size);
return -EINVAL;
}
map = kmalloc(sizeof(struct ip_set_iptree), GFP_KERNEL);
if (!map) {
DP("out of memory for %d bytes",
sizeof(struct ip_set_iptree));
return -ENOMEM;
}
memset(map, 0, sizeof(*map));
map->timeout = req->timeout;
map->elements = 0;
set->data = map;
init_gc_timer(set);
return 0;
}
static inline void
__flush(struct ip_set_iptree *map)
{
struct ip_set_iptreeb *btree;
struct ip_set_iptreec *ctree;
struct ip_set_iptreed *dtree;
unsigned int a,b,c;
LOOP_WALK_BEGIN(map, a, btree);
LOOP_WALK_BEGIN(btree, b, ctree);
LOOP_WALK_BEGIN(ctree, c, dtree);
kmem_cache_free(leaf_cachep, dtree);
LOOP_WALK_END;
kmem_cache_free(branch_cachep, ctree);
LOOP_WALK_END;
kmem_cache_free(branch_cachep, btree);
LOOP_WALK_END;
map->elements = 0;
}
static void
iptree_destroy(struct ip_set *set)
{
struct ip_set_iptree *map = set->data;
/* gc might be running */
while (!del_timer(&map->gc))
msleep(IPTREE_DESTROY_SLEEP);
__flush(map);
kfree(map);
set->data = NULL;
}
static void
iptree_flush(struct ip_set *set)
{
struct ip_set_iptree *map = set->data;
unsigned int timeout = map->timeout;
/* gc might be running */
while (!del_timer(&map->gc))
msleep(IPTREE_DESTROY_SLEEP);
__flush(map);
memset(map, 0, sizeof(*map));
map->timeout = timeout;
init_gc_timer(set);
}
static void
iptree_list_header(const struct ip_set *set, void *data)
{
const struct ip_set_iptree *map = set->data;
struct ip_set_req_iptree_create *header = data;
header->timeout = map->timeout;
}
static int
iptree_list_members_size(const struct ip_set *set)
{
const struct ip_set_iptree *map = set->data;
struct ip_set_iptreeb *btree;
struct ip_set_iptreec *ctree;
struct ip_set_iptreed *dtree;
unsigned int a,b,c,d;
unsigned int count = 0;
LOOP_WALK_BEGIN(map, a, btree);
LOOP_WALK_BEGIN(btree, b, ctree);
LOOP_WALK_BEGIN(ctree, c, dtree);
for (d = 0; d < 256; d++) {
if (dtree->expires[d]
&& (!map->timeout || time_after(dtree->expires[d], jiffies)))
count++;
}
LOOP_WALK_END;
LOOP_WALK_END;
LOOP_WALK_END;
DP("members %u", count);
return (count * sizeof(struct ip_set_req_iptree));
}
static void
iptree_list_members(const struct ip_set *set, void *data)
{
const struct ip_set_iptree *map = set->data;
struct ip_set_iptreeb *btree;
struct ip_set_iptreec *ctree;
struct ip_set_iptreed *dtree;
unsigned int a,b,c,d;
size_t offset = 0;
struct ip_set_req_iptree *entry;
LOOP_WALK_BEGIN(map, a, btree);
LOOP_WALK_BEGIN(btree, b, ctree);
LOOP_WALK_BEGIN(ctree, c, dtree);
for (d = 0; d < 256; d++) {
if (dtree->expires[d]
&& (!map->timeout || time_after(dtree->expires[d], jiffies))) {
entry = data + offset;
entry->ip = ((a << 24) | (b << 16) | (c << 8) | d);
entry->timeout = !map->timeout ? 0
: (dtree->expires[d] - jiffies)/HZ;
offset += sizeof(struct ip_set_req_iptree);
}
}
LOOP_WALK_END;
LOOP_WALK_END;
LOOP_WALK_END;
}
IP_SET_TYPE(iptree, IPSET_TYPE_IP | IPSET_DATA_SINGLE)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
MODULE_DESCRIPTION("iptree type of IP sets");
module_param(limit, int, 0600);
MODULE_PARM_DESC(limit, "maximal number of elements stored in the sets");
static int __init ip_set_iptree_init(void)
{
int ret;
branch_cachep = KMEM_CACHE_CREATE("ip_set_iptreeb",
sizeof(struct ip_set_iptreeb));
if (!branch_cachep) {
printk(KERN_ERR "Unable to create ip_set_iptreeb slab cache\n");
ret = -ENOMEM;
goto out;
}
leaf_cachep = KMEM_CACHE_CREATE("ip_set_iptreed",
sizeof(struct ip_set_iptreed));
if (!leaf_cachep) {
printk(KERN_ERR "Unable to create ip_set_iptreed slab cache\n");
ret = -ENOMEM;
goto free_branch;
}
ret = ip_set_register_set_type(&ip_set_iptree);
if (ret == 0)
goto out;
kmem_cache_destroy(leaf_cachep);
free_branch:
kmem_cache_destroy(branch_cachep);
out:
return ret;
}
static void __exit ip_set_iptree_fini(void)
{
/* FIXME: possible race with ip_set_create() */
ip_set_unregister_set_type(&ip_set_iptree);
kmem_cache_destroy(leaf_cachep);
kmem_cache_destroy(branch_cachep);
}
module_init(ip_set_iptree_init);
module_exit(ip_set_iptree_fini);