2a43c4af3f
Around jiffies wrap time (i.e. within first 5 mins after boot), recent match rules which contain both --seconds and --hitcount arguments experience false matches. This is because the last_pkts array is filled with zeros on creation, and when comparing 'now' to 0 (+ --seconds argument), time_before_eq thinks it has found a hit. Below patch adds a break if the packet value is zero. This has the unfortunate side effect of causing mismatches if a packet was received when jiffies really was equal to zero. The odds of that happening are slim compared to the problems caused by not adding the break however. Plus, the author used this same method just below, so it is "good enough". This fixes netfilter bugs #383 and #395. Signed-off-by: Phil Oester <kernel@linuxace.com> Signed-off-by: Patrick McHardy <kaber@trash.net> Signed-off-by: David S. Miller <davem@davemloft.net>
1004 lines
33 KiB
C
1004 lines
33 KiB
C
/* Kernel module to check if the source address has been seen recently. */
|
|
/* Copyright 2002-2003, Stephen Frost, 2.5.x port by laforge@netfilter.org */
|
|
/* Author: Stephen Frost <sfrost@snowman.net> */
|
|
/* Project Page: http://snowman.net/projects/ipt_recent/ */
|
|
/* This software is distributed under the terms of the GPL, Version 2 */
|
|
/* This copyright does not cover user programs that use kernel services
|
|
* by normal system calls. */
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/interrupt.h>
|
|
#include <asm/uaccess.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/netfilter_ipv4/ip_tables.h>
|
|
#include <linux/netfilter_ipv4/ipt_recent.h>
|
|
|
|
#undef DEBUG
|
|
#define HASH_LOG 9
|
|
|
|
/* Defaults, these can be overridden on the module command-line. */
|
|
static int ip_list_tot = 100;
|
|
static int ip_pkt_list_tot = 20;
|
|
static int ip_list_hash_size = 0;
|
|
static int ip_list_perms = 0644;
|
|
#ifdef DEBUG
|
|
static int debug = 1;
|
|
#endif
|
|
|
|
static char version[] =
|
|
KERN_INFO RECENT_NAME " " RECENT_VER ": Stephen Frost <sfrost@snowman.net>. http://snowman.net/projects/ipt_recent/\n";
|
|
|
|
MODULE_AUTHOR("Stephen Frost <sfrost@snowman.net>");
|
|
MODULE_DESCRIPTION("IP tables recently seen matching module " RECENT_VER);
|
|
MODULE_LICENSE("GPL");
|
|
module_param(ip_list_tot, int, 0400);
|
|
module_param(ip_pkt_list_tot, int, 0400);
|
|
module_param(ip_list_hash_size, int, 0400);
|
|
module_param(ip_list_perms, int, 0400);
|
|
#ifdef DEBUG
|
|
module_param(debug, int, 0600);
|
|
MODULE_PARM_DESC(debug,"debugging level, defaults to 1");
|
|
#endif
|
|
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");
|
|
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");
|
|
|
|
/* Structure of our list of recently seen addresses. */
|
|
struct recent_ip_list {
|
|
u_int32_t addr;
|
|
u_int8_t ttl;
|
|
unsigned long last_seen;
|
|
unsigned long *last_pkts;
|
|
u_int32_t oldest_pkt;
|
|
u_int32_t hash_entry;
|
|
u_int32_t time_pos;
|
|
};
|
|
|
|
struct time_info_list {
|
|
u_int32_t position;
|
|
u_int32_t time;
|
|
};
|
|
|
|
/* Structure of our linked list of tables of recent lists. */
|
|
struct recent_ip_tables {
|
|
char name[IPT_RECENT_NAME_LEN];
|
|
int count;
|
|
int time_pos;
|
|
struct recent_ip_list *table;
|
|
struct recent_ip_tables *next;
|
|
spinlock_t list_lock;
|
|
int *hash_table;
|
|
struct time_info_list *time_info;
|
|
#ifdef CONFIG_PROC_FS
|
|
struct proc_dir_entry *status_proc;
|
|
#endif /* CONFIG_PROC_FS */
|
|
};
|
|
|
|
/* Our current list of addresses we have recently seen.
|
|
* Only added to on a --set, and only updated on --set || --update
|
|
*/
|
|
static struct recent_ip_tables *r_tables = NULL;
|
|
|
|
/* We protect r_list with this spinlock so two processors are not modifying
|
|
* the list at the same time.
|
|
*/
|
|
static DEFINE_SPINLOCK(recent_lock);
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
/* Our /proc/net/ipt_recent entry */
|
|
static struct proc_dir_entry *proc_net_ipt_recent = NULL;
|
|
#endif
|
|
|
|
/* Function declaration for later. */
|
|
static int
|
|
match(const struct sk_buff *skb,
|
|
const struct net_device *in,
|
|
const struct net_device *out,
|
|
const void *matchinfo,
|
|
int offset,
|
|
int *hotdrop);
|
|
|
|
/* Function to hash a given address into the hash table of table_size size */
|
|
static int hash_func(unsigned int addr, int table_size)
|
|
{
|
|
int result = 0;
|
|
unsigned int value = addr;
|
|
do { result ^= value; } while((value >>= HASH_LOG));
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": %d = hash_func(%u,%d)\n",
|
|
result & (table_size - 1),
|
|
addr,
|
|
table_size);
|
|
#endif
|
|
|
|
return(result & (table_size - 1));
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
/* This is the function which produces the output for our /proc output
|
|
* interface which lists each IP address, the last seen time and the
|
|
* other recent times the address was seen.
|
|
*/
|
|
|
|
static int ip_recent_get_info(char *buffer, char **start, off_t offset, int length, int *eof, void *data)
|
|
{
|
|
int len = 0, count, last_len = 0, pkt_count;
|
|
off_t pos = 0;
|
|
off_t begin = 0;
|
|
struct recent_ip_tables *curr_table;
|
|
|
|
curr_table = (struct recent_ip_tables*) data;
|
|
|
|
spin_lock_bh(&curr_table->list_lock);
|
|
for(count = 0; count < ip_list_tot; count++) {
|
|
if(!curr_table->table[count].addr) continue;
|
|
last_len = len;
|
|
len += sprintf(buffer+len,"src=%u.%u.%u.%u ",NIPQUAD(curr_table->table[count].addr));
|
|
len += sprintf(buffer+len,"ttl: %u ",curr_table->table[count].ttl);
|
|
len += sprintf(buffer+len,"last_seen: %lu ",curr_table->table[count].last_seen);
|
|
len += sprintf(buffer+len,"oldest_pkt: %u ",curr_table->table[count].oldest_pkt);
|
|
len += sprintf(buffer+len,"last_pkts: %lu",curr_table->table[count].last_pkts[0]);
|
|
for(pkt_count = 1; pkt_count < ip_pkt_list_tot; pkt_count++) {
|
|
if(!curr_table->table[count].last_pkts[pkt_count]) break;
|
|
len += sprintf(buffer+len,", %lu",curr_table->table[count].last_pkts[pkt_count]);
|
|
}
|
|
len += sprintf(buffer+len,"\n");
|
|
pos = begin + len;
|
|
if(pos < offset) { len = 0; begin = pos; }
|
|
if(pos > offset + length) { len = last_len; break; }
|
|
}
|
|
|
|
*start = buffer + (offset - begin);
|
|
len -= (offset - begin);
|
|
if(len > length) len = length;
|
|
|
|
spin_unlock_bh(&curr_table->list_lock);
|
|
return len;
|
|
}
|
|
|
|
/* ip_recent_ctrl provides an interface for users to modify the table
|
|
* directly. This allows adding entries, removing entries, and
|
|
* flushing the entire table.
|
|
* This is done by opening up the appropriate table for writing and
|
|
* sending one of:
|
|
* xx.xx.xx.xx -- Add entry to table with current time
|
|
* +xx.xx.xx.xx -- Add entry to table with current time
|
|
* -xx.xx.xx.xx -- Remove entry from table
|
|
* clear -- Flush table, remove all entries
|
|
*/
|
|
|
|
static int ip_recent_ctrl(struct file *file, const char __user *input, unsigned long size, void *data)
|
|
{
|
|
static const u_int32_t max[4] = { 0xffffffff, 0xffffff, 0xffff, 0xff };
|
|
u_int32_t val;
|
|
int base, used = 0;
|
|
char c, *cp;
|
|
union iaddr {
|
|
uint8_t bytes[4];
|
|
uint32_t word;
|
|
} res;
|
|
uint8_t *pp = res.bytes;
|
|
int digit;
|
|
|
|
char buffer[20];
|
|
int len, check_set = 0, count;
|
|
u_int32_t addr = 0;
|
|
struct sk_buff *skb;
|
|
struct ipt_recent_info *info;
|
|
struct recent_ip_tables *curr_table;
|
|
|
|
curr_table = (struct recent_ip_tables*) data;
|
|
|
|
if(size > 20) len = 20; else len = size;
|
|
|
|
if(copy_from_user(buffer,input,len)) return -EFAULT;
|
|
|
|
if(len < 20) buffer[len] = '\0';
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": ip_recent_ctrl len: %d, input: `%.20s'\n",len,buffer);
|
|
#endif
|
|
|
|
cp = buffer;
|
|
while(isspace(*cp)) { cp++; used++; if(used >= len-5) return used; }
|
|
|
|
/* Check if we are asked to flush the entire table */
|
|
if(!memcmp(cp,"clear",5)) {
|
|
used += 5;
|
|
spin_lock_bh(&curr_table->list_lock);
|
|
curr_table->time_pos = 0;
|
|
for(count = 0; count < ip_list_hash_size; count++) {
|
|
curr_table->hash_table[count] = -1;
|
|
}
|
|
for(count = 0; count < ip_list_tot; count++) {
|
|
curr_table->table[count].last_seen = 0;
|
|
curr_table->table[count].addr = 0;
|
|
curr_table->table[count].ttl = 0;
|
|
memset(curr_table->table[count].last_pkts,0,ip_pkt_list_tot*sizeof(unsigned long));
|
|
curr_table->table[count].oldest_pkt = 0;
|
|
curr_table->table[count].time_pos = 0;
|
|
curr_table->time_info[count].position = count;
|
|
curr_table->time_info[count].time = 0;
|
|
}
|
|
spin_unlock_bh(&curr_table->list_lock);
|
|
return used;
|
|
}
|
|
|
|
check_set = IPT_RECENT_SET;
|
|
switch(*cp) {
|
|
case '+': check_set = IPT_RECENT_SET; cp++; used++; break;
|
|
case '-': check_set = IPT_RECENT_REMOVE; cp++; used++; break;
|
|
default: if(!isdigit(*cp)) return (used+1); break;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": ip_recent_ctrl cp: `%c', check_set: %d\n",*cp,check_set);
|
|
#endif
|
|
/* Get addr (effectively inet_aton()) */
|
|
/* Shamelessly stolen from libc, a function in the kernel for doing
|
|
* this would, of course, be greatly preferred, but our options appear
|
|
* to be rather limited, so we will just do it ourselves here.
|
|
*/
|
|
res.word = 0;
|
|
|
|
c = *cp;
|
|
for(;;) {
|
|
if(!isdigit(c)) return used;
|
|
val = 0; base = 10; digit = 0;
|
|
if(c == '0') {
|
|
c = *++cp;
|
|
if(c == 'x' || c == 'X') base = 16, c = *++cp;
|
|
else { base = 8; digit = 1; }
|
|
}
|
|
for(;;) {
|
|
if(isascii(c) && isdigit(c)) {
|
|
if(base == 8 && (c == '8' || c == '0')) return used;
|
|
val = (val * base) + (c - '0');
|
|
c = *++cp;
|
|
digit = 1;
|
|
} else if(base == 16 && isascii(c) && isxdigit(c)) {
|
|
val = (val << 4) | (c + 10 - (islower(c) ? 'a' : 'A'));
|
|
c = *++cp;
|
|
digit = 1;
|
|
} else break;
|
|
}
|
|
if(c == '.') {
|
|
if(pp > res.bytes + 2 || val > 0xff) return used;
|
|
*pp++ = val;
|
|
c = *++cp;
|
|
} else break;
|
|
}
|
|
used = cp - buffer;
|
|
if(c != '\0' && (!isascii(c) || !isspace(c))) return used;
|
|
if(c == '\n') used++;
|
|
if(!digit) return used;
|
|
|
|
if(val > max[pp - res.bytes]) return used;
|
|
addr = res.word | htonl(val);
|
|
|
|
if(!addr && check_set == IPT_RECENT_SET) return used;
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": ip_recent_ctrl c: %c, addr: %u used: %d\n",c,addr,used);
|
|
#endif
|
|
|
|
/* Set up and just call match */
|
|
info = kmalloc(sizeof(struct ipt_recent_info),GFP_KERNEL);
|
|
if(!info) { return -ENOMEM; }
|
|
info->seconds = 0;
|
|
info->hit_count = 0;
|
|
info->check_set = check_set;
|
|
info->invert = 0;
|
|
info->side = IPT_RECENT_SOURCE;
|
|
strncpy(info->name,curr_table->name,IPT_RECENT_NAME_LEN);
|
|
info->name[IPT_RECENT_NAME_LEN-1] = '\0';
|
|
|
|
skb = kmalloc(sizeof(struct sk_buff),GFP_KERNEL);
|
|
if (!skb) {
|
|
used = -ENOMEM;
|
|
goto out_free_info;
|
|
}
|
|
skb->nh.iph = kmalloc(sizeof(struct iphdr),GFP_KERNEL);
|
|
if (!skb->nh.iph) {
|
|
used = -ENOMEM;
|
|
goto out_free_skb;
|
|
}
|
|
|
|
skb->nh.iph->saddr = addr;
|
|
skb->nh.iph->daddr = 0;
|
|
/* Clear ttl since we have no way of knowing it */
|
|
skb->nh.iph->ttl = 0;
|
|
match(skb,NULL,NULL,info,0,NULL);
|
|
|
|
kfree(skb->nh.iph);
|
|
out_free_skb:
|
|
kfree(skb);
|
|
out_free_info:
|
|
kfree(info);
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": Leaving ip_recent_ctrl addr: %u used: %d\n",addr,used);
|
|
#endif
|
|
return used;
|
|
}
|
|
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
/* 'match' is our primary function, called by the kernel whenever a rule is
|
|
* hit with our module as an option to it.
|
|
* What this function does depends on what was specifically asked of it by
|
|
* the user:
|
|
* --set -- Add or update last seen time of the source address of the packet
|
|
* -- matchinfo->check_set == IPT_RECENT_SET
|
|
* --rcheck -- Just check if the source address is in the list
|
|
* -- matchinfo->check_set == IPT_RECENT_CHECK
|
|
* --update -- If the source address is in the list, update last_seen
|
|
* -- matchinfo->check_set == IPT_RECENT_UPDATE
|
|
* --remove -- If the source address is in the list, remove it
|
|
* -- matchinfo->check_set == IPT_RECENT_REMOVE
|
|
* --seconds -- Option to --rcheck/--update, only match if last_seen within seconds
|
|
* -- matchinfo->seconds
|
|
* --hitcount -- Option to --rcheck/--update, only match if seen hitcount times
|
|
* -- matchinfo->hit_count
|
|
* --seconds and --hitcount can be combined
|
|
*/
|
|
static int
|
|
match(const struct sk_buff *skb,
|
|
const struct net_device *in,
|
|
const struct net_device *out,
|
|
const void *matchinfo,
|
|
int offset,
|
|
int *hotdrop)
|
|
{
|
|
int pkt_count, hits_found, ans;
|
|
unsigned long now;
|
|
const struct ipt_recent_info *info = matchinfo;
|
|
u_int32_t addr = 0, time_temp;
|
|
u_int8_t ttl = skb->nh.iph->ttl;
|
|
int *hash_table;
|
|
int orig_hash_result, hash_result, temp, location = 0, time_loc, end_collision_chain = -1;
|
|
struct time_info_list *time_info;
|
|
struct recent_ip_tables *curr_table;
|
|
struct recent_ip_tables *last_table;
|
|
struct recent_ip_list *r_list;
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match() called\n");
|
|
#endif
|
|
|
|
/* Default is false ^ info->invert */
|
|
ans = info->invert;
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): name = '%s'\n",info->name);
|
|
#endif
|
|
|
|
/* if out != NULL then routing has been done and TTL changed.
|
|
* We change it back here internally for match what came in before routing. */
|
|
if(out) ttl++;
|
|
|
|
/* Find the right table */
|
|
spin_lock_bh(&recent_lock);
|
|
curr_table = r_tables;
|
|
while( (last_table = curr_table) && strncmp(info->name,curr_table->name,IPT_RECENT_NAME_LEN) && (curr_table = curr_table->next) );
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): table found('%s')\n",info->name);
|
|
#endif
|
|
|
|
spin_unlock_bh(&recent_lock);
|
|
|
|
/* Table with this name not found, match impossible */
|
|
if(!curr_table) { return ans; }
|
|
|
|
/* Make sure no one is changing the list while we work with it */
|
|
spin_lock_bh(&curr_table->list_lock);
|
|
|
|
r_list = curr_table->table;
|
|
if(info->side == IPT_RECENT_DEST) addr = skb->nh.iph->daddr; else addr = skb->nh.iph->saddr;
|
|
|
|
if(!addr) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match() address (%u) invalid, leaving.\n",addr);
|
|
#endif
|
|
spin_unlock_bh(&curr_table->list_lock);
|
|
return ans;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): checking table, addr: %u, ttl: %u, orig_ttl: %u\n",addr,ttl,skb->nh.iph->ttl);
|
|
#endif
|
|
|
|
/* Get jiffies now in case they changed while we were waiting for a lock */
|
|
now = jiffies;
|
|
hash_table = curr_table->hash_table;
|
|
time_info = curr_table->time_info;
|
|
|
|
orig_hash_result = hash_result = hash_func(addr,ip_list_hash_size);
|
|
/* Hash entry at this result used */
|
|
/* Check for TTL match if requested. If TTL is zero then a match would never
|
|
* happen, so match regardless of existing TTL in that case. Zero means the
|
|
* entry was added via the /proc interface anyway, so we will just use the
|
|
* first TTL we get for that IP address. */
|
|
if(info->check_set & IPT_RECENT_TTL) {
|
|
while(hash_table[hash_result] != -1 && !(r_list[hash_table[hash_result]].addr == addr &&
|
|
(!r_list[hash_table[hash_result]].ttl || r_list[hash_table[hash_result]].ttl == ttl))) {
|
|
/* Collision in hash table */
|
|
hash_result = (hash_result + 1) % ip_list_hash_size;
|
|
}
|
|
} else {
|
|
while(hash_table[hash_result] != -1 && r_list[hash_table[hash_result]].addr != addr) {
|
|
/* Collision in hash table */
|
|
hash_result = (hash_result + 1) % ip_list_hash_size;
|
|
}
|
|
}
|
|
|
|
if(hash_table[hash_result] == -1 && !(info->check_set & IPT_RECENT_SET)) {
|
|
/* IP not in list and not asked to SET */
|
|
spin_unlock_bh(&curr_table->list_lock);
|
|
return ans;
|
|
}
|
|
|
|
/* Check if we need to handle the collision, do not need to on REMOVE */
|
|
if(orig_hash_result != hash_result && !(info->check_set & IPT_RECENT_REMOVE)) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): Collision in hash table. (or: %d,hr: %d,oa: %u,ha: %u)\n",
|
|
orig_hash_result,
|
|
hash_result,
|
|
r_list[hash_table[orig_hash_result]].addr,
|
|
addr);
|
|
#endif
|
|
|
|
/* We had a collision.
|
|
* orig_hash_result is where we started, hash_result is where we ended up.
|
|
* So, swap them because we are likely to see the same guy again sooner */
|
|
#ifdef DEBUG
|
|
if(debug) {
|
|
printk(KERN_INFO RECENT_NAME ": match(): Collision; hash_table[orig_hash_result] = %d\n",hash_table[orig_hash_result]);
|
|
printk(KERN_INFO RECENT_NAME ": match(): Collision; r_list[hash_table[orig_hash_result]].hash_entry = %d\n",
|
|
r_list[hash_table[orig_hash_result]].hash_entry);
|
|
}
|
|
#endif
|
|
|
|
r_list[hash_table[orig_hash_result]].hash_entry = hash_result;
|
|
|
|
|
|
temp = hash_table[orig_hash_result];
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): Collision; hash_table[hash_result] = %d\n",hash_table[hash_result]);
|
|
#endif
|
|
hash_table[orig_hash_result] = hash_table[hash_result];
|
|
hash_table[hash_result] = temp;
|
|
temp = hash_result;
|
|
hash_result = orig_hash_result;
|
|
orig_hash_result = temp;
|
|
time_info[r_list[hash_table[orig_hash_result]].time_pos].position = hash_table[orig_hash_result];
|
|
if(hash_table[hash_result] != -1) {
|
|
r_list[hash_table[hash_result]].hash_entry = hash_result;
|
|
time_info[r_list[hash_table[hash_result]].time_pos].position = hash_table[hash_result];
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): Collision handled.\n");
|
|
#endif
|
|
}
|
|
|
|
if(hash_table[hash_result] == -1) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): New table entry. (hr: %d,ha: %u)\n",
|
|
hash_result, addr);
|
|
#endif
|
|
|
|
/* New item found and IPT_RECENT_SET, so we need to add it */
|
|
location = time_info[curr_table->time_pos].position;
|
|
hash_table[r_list[location].hash_entry] = -1;
|
|
hash_table[hash_result] = location;
|
|
memset(r_list[location].last_pkts,0,ip_pkt_list_tot*sizeof(unsigned long));
|
|
r_list[location].time_pos = curr_table->time_pos;
|
|
r_list[location].addr = addr;
|
|
r_list[location].ttl = ttl;
|
|
r_list[location].last_seen = now;
|
|
r_list[location].oldest_pkt = 1;
|
|
r_list[location].last_pkts[0] = now;
|
|
r_list[location].hash_entry = hash_result;
|
|
time_info[curr_table->time_pos].time = r_list[location].last_seen;
|
|
curr_table->time_pos = (curr_table->time_pos + 1) % ip_list_tot;
|
|
|
|
ans = !info->invert;
|
|
} else {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): Existing table entry. (hr: %d,ha: %u)\n",
|
|
hash_result,
|
|
addr);
|
|
#endif
|
|
|
|
/* Existing item found */
|
|
location = hash_table[hash_result];
|
|
/* We have a match on address, now to make sure it meets all requirements for a
|
|
* full match. */
|
|
if(info->check_set & IPT_RECENT_CHECK || info->check_set & IPT_RECENT_UPDATE) {
|
|
if(!info->seconds && !info->hit_count) ans = !info->invert; else ans = info->invert;
|
|
if(info->seconds && !info->hit_count) {
|
|
if(time_before_eq(now,r_list[location].last_seen+info->seconds*HZ)) ans = !info->invert; else ans = info->invert;
|
|
}
|
|
if(info->seconds && info->hit_count) {
|
|
for(pkt_count = 0, hits_found = 0; pkt_count < ip_pkt_list_tot; pkt_count++) {
|
|
if(r_list[location].last_pkts[pkt_count] == 0) break;
|
|
if(time_before_eq(now,r_list[location].last_pkts[pkt_count]+info->seconds*HZ)) hits_found++;
|
|
}
|
|
if(hits_found >= info->hit_count) ans = !info->invert; else ans = info->invert;
|
|
}
|
|
if(info->hit_count && !info->seconds) {
|
|
for(pkt_count = 0, hits_found = 0; pkt_count < ip_pkt_list_tot; pkt_count++) {
|
|
if(r_list[location].last_pkts[pkt_count] == 0) break;
|
|
hits_found++;
|
|
}
|
|
if(hits_found >= info->hit_count) ans = !info->invert; else ans = info->invert;
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
if(debug) {
|
|
if(ans)
|
|
printk(KERN_INFO RECENT_NAME ": match(): match addr: %u\n",addr);
|
|
else
|
|
printk(KERN_INFO RECENT_NAME ": match(): no match addr: %u\n",addr);
|
|
}
|
|
#endif
|
|
|
|
/* If and only if we have been asked to SET, or to UPDATE (on match) do we add the
|
|
* current timestamp to the last_seen. */
|
|
if((info->check_set & IPT_RECENT_SET && (ans = !info->invert)) || (info->check_set & IPT_RECENT_UPDATE && ans)) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): SET or UPDATE; updating time info.\n");
|
|
#endif
|
|
/* Have to update our time info */
|
|
time_loc = r_list[location].time_pos;
|
|
time_info[time_loc].time = now;
|
|
time_info[time_loc].position = location;
|
|
while((time_info[(time_loc+1) % ip_list_tot].time < time_info[time_loc].time) && ((time_loc+1) % ip_list_tot) != curr_table->time_pos) {
|
|
time_temp = time_info[time_loc].time;
|
|
time_info[time_loc].time = time_info[(time_loc+1)%ip_list_tot].time;
|
|
time_info[(time_loc+1)%ip_list_tot].time = time_temp;
|
|
time_temp = time_info[time_loc].position;
|
|
time_info[time_loc].position = time_info[(time_loc+1)%ip_list_tot].position;
|
|
time_info[(time_loc+1)%ip_list_tot].position = time_temp;
|
|
r_list[time_info[time_loc].position].time_pos = time_loc;
|
|
r_list[time_info[(time_loc+1)%ip_list_tot].position].time_pos = (time_loc+1)%ip_list_tot;
|
|
time_loc = (time_loc+1) % ip_list_tot;
|
|
}
|
|
r_list[location].time_pos = time_loc;
|
|
r_list[location].ttl = ttl;
|
|
r_list[location].last_pkts[r_list[location].oldest_pkt] = now;
|
|
r_list[location].oldest_pkt = ++r_list[location].oldest_pkt % ip_pkt_list_tot;
|
|
r_list[location].last_seen = now;
|
|
}
|
|
/* If we have been asked to remove the entry from the list, just set it to 0 */
|
|
if(info->check_set & IPT_RECENT_REMOVE) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): REMOVE; clearing entry (or: %d, hr: %d).\n",orig_hash_result,hash_result);
|
|
#endif
|
|
/* Check if this is part of a collision chain */
|
|
while(hash_table[(orig_hash_result+1) % ip_list_hash_size] != -1) {
|
|
orig_hash_result++;
|
|
if(hash_func(r_list[hash_table[orig_hash_result]].addr,ip_list_hash_size) == hash_result) {
|
|
/* Found collision chain, how deep does this rabbit hole go? */
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): REMOVE; found collision chain.\n");
|
|
#endif
|
|
end_collision_chain = orig_hash_result;
|
|
}
|
|
}
|
|
if(end_collision_chain != -1) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match(): REMOVE; part of collision chain, moving to end.\n");
|
|
#endif
|
|
/* Part of a collision chain, swap it with the end of the chain
|
|
* before removing. */
|
|
r_list[hash_table[end_collision_chain]].hash_entry = hash_result;
|
|
temp = hash_table[end_collision_chain];
|
|
hash_table[end_collision_chain] = hash_table[hash_result];
|
|
hash_table[hash_result] = temp;
|
|
time_info[r_list[hash_table[hash_result]].time_pos].position = hash_table[hash_result];
|
|
hash_result = end_collision_chain;
|
|
r_list[hash_table[hash_result]].hash_entry = hash_result;
|
|
time_info[r_list[hash_table[hash_result]].time_pos].position = hash_table[hash_result];
|
|
}
|
|
location = hash_table[hash_result];
|
|
hash_table[r_list[location].hash_entry] = -1;
|
|
time_loc = r_list[location].time_pos;
|
|
time_info[time_loc].time = 0;
|
|
time_info[time_loc].position = location;
|
|
while((time_info[(time_loc+1) % ip_list_tot].time < time_info[time_loc].time) && ((time_loc+1) % ip_list_tot) != curr_table->time_pos) {
|
|
time_temp = time_info[time_loc].time;
|
|
time_info[time_loc].time = time_info[(time_loc+1)%ip_list_tot].time;
|
|
time_info[(time_loc+1)%ip_list_tot].time = time_temp;
|
|
time_temp = time_info[time_loc].position;
|
|
time_info[time_loc].position = time_info[(time_loc+1)%ip_list_tot].position;
|
|
time_info[(time_loc+1)%ip_list_tot].position = time_temp;
|
|
r_list[time_info[time_loc].position].time_pos = time_loc;
|
|
r_list[time_info[(time_loc+1)%ip_list_tot].position].time_pos = (time_loc+1)%ip_list_tot;
|
|
time_loc = (time_loc+1) % ip_list_tot;
|
|
}
|
|
r_list[location].time_pos = time_loc;
|
|
r_list[location].last_seen = 0;
|
|
r_list[location].addr = 0;
|
|
r_list[location].ttl = 0;
|
|
memset(r_list[location].last_pkts,0,ip_pkt_list_tot*sizeof(unsigned long));
|
|
r_list[location].oldest_pkt = 0;
|
|
ans = !info->invert;
|
|
}
|
|
spin_unlock_bh(&curr_table->list_lock);
|
|
return ans;
|
|
}
|
|
|
|
spin_unlock_bh(&curr_table->list_lock);
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": match() left.\n");
|
|
#endif
|
|
return ans;
|
|
}
|
|
|
|
/* This function is to verify that the rule given during the userspace iptables
|
|
* command is correct.
|
|
* If the command is valid then we check if the table name referred to by the
|
|
* rule exists, if not it is created.
|
|
*/
|
|
static int
|
|
checkentry(const char *tablename,
|
|
const struct ipt_ip *ip,
|
|
void *matchinfo,
|
|
unsigned int matchsize,
|
|
unsigned int hook_mask)
|
|
{
|
|
int flag = 0, c;
|
|
unsigned long *hold;
|
|
const struct ipt_recent_info *info = matchinfo;
|
|
struct recent_ip_tables *curr_table, *find_table, *last_table;
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry() entered.\n");
|
|
#endif
|
|
|
|
if (matchsize != IPT_ALIGN(sizeof(struct ipt_recent_info))) return 0;
|
|
|
|
/* seconds and hit_count only valid for CHECK/UPDATE */
|
|
if(info->check_set & IPT_RECENT_SET) { flag++; if(info->seconds || info->hit_count) return 0; }
|
|
if(info->check_set & IPT_RECENT_REMOVE) { flag++; if(info->seconds || info->hit_count) return 0; }
|
|
if(info->check_set & IPT_RECENT_CHECK) flag++;
|
|
if(info->check_set & IPT_RECENT_UPDATE) flag++;
|
|
|
|
/* One and only one of these should ever be set */
|
|
if(flag != 1) return 0;
|
|
|
|
/* Name must be set to something */
|
|
if(!info->name || !info->name[0]) return 0;
|
|
|
|
/* Things look good, create a list for this if it does not exist */
|
|
/* Lock the linked list while we play with it */
|
|
spin_lock_bh(&recent_lock);
|
|
|
|
/* Look for an entry with this name already created */
|
|
/* Finds the end of the list and the entry before the end if current name does not exist */
|
|
find_table = r_tables;
|
|
while( (last_table = find_table) && strncmp(info->name,find_table->name,IPT_RECENT_NAME_LEN) && (find_table = find_table->next) );
|
|
|
|
/* If a table already exists just increment the count on that table and return */
|
|
if(find_table) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: table found (%s), incrementing count.\n",info->name);
|
|
#endif
|
|
find_table->count++;
|
|
spin_unlock_bh(&recent_lock);
|
|
return 1;
|
|
}
|
|
|
|
spin_unlock_bh(&recent_lock);
|
|
|
|
/* Table with this name not found */
|
|
/* Allocate memory for new linked list item */
|
|
|
|
#ifdef DEBUG
|
|
if(debug) {
|
|
printk(KERN_INFO RECENT_NAME ": checkentry: no table found (%s)\n",info->name);
|
|
printk(KERN_INFO RECENT_NAME ": checkentry: Allocationg %d for link-list entry.\n",sizeof(struct recent_ip_tables));
|
|
}
|
|
#endif
|
|
|
|
curr_table = vmalloc(sizeof(struct recent_ip_tables));
|
|
if(curr_table == NULL) return 0;
|
|
|
|
spin_lock_init(&curr_table->list_lock);
|
|
curr_table->next = NULL;
|
|
curr_table->count = 1;
|
|
curr_table->time_pos = 0;
|
|
strncpy(curr_table->name,info->name,IPT_RECENT_NAME_LEN);
|
|
curr_table->name[IPT_RECENT_NAME_LEN-1] = '\0';
|
|
|
|
/* Allocate memory for this table and the list of packets in each entry. */
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: Allocating %d for table (%s).\n",
|
|
sizeof(struct recent_ip_list)*ip_list_tot,
|
|
info->name);
|
|
#endif
|
|
|
|
curr_table->table = vmalloc(sizeof(struct recent_ip_list)*ip_list_tot);
|
|
if(curr_table->table == NULL) { vfree(curr_table); return 0; }
|
|
memset(curr_table->table,0,sizeof(struct recent_ip_list)*ip_list_tot);
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: Allocating %d for pkt_list.\n",
|
|
sizeof(unsigned long)*ip_pkt_list_tot*ip_list_tot);
|
|
#endif
|
|
|
|
hold = vmalloc(sizeof(unsigned long)*ip_pkt_list_tot*ip_list_tot);
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: After pkt_list allocation.\n");
|
|
#endif
|
|
if(hold == NULL) {
|
|
printk(KERN_INFO RECENT_NAME ": checkentry: unable to allocate for pkt_list.\n");
|
|
vfree(curr_table->table);
|
|
vfree(curr_table);
|
|
return 0;
|
|
}
|
|
for(c = 0; c < ip_list_tot; c++) {
|
|
curr_table->table[c].last_pkts = hold + c*ip_pkt_list_tot;
|
|
}
|
|
|
|
/* Allocate memory for the hash table */
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: Allocating %d for hash_table.\n",
|
|
sizeof(int)*ip_list_hash_size);
|
|
#endif
|
|
|
|
curr_table->hash_table = vmalloc(sizeof(int)*ip_list_hash_size);
|
|
if(!curr_table->hash_table) {
|
|
printk(KERN_INFO RECENT_NAME ": checkentry: unable to allocate for hash_table.\n");
|
|
vfree(hold);
|
|
vfree(curr_table->table);
|
|
vfree(curr_table);
|
|
return 0;
|
|
}
|
|
|
|
for(c = 0; c < ip_list_hash_size; c++) {
|
|
curr_table->hash_table[c] = -1;
|
|
}
|
|
|
|
/* Allocate memory for the time info */
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: Allocating %d for time_info.\n",
|
|
sizeof(struct time_info_list)*ip_list_tot);
|
|
#endif
|
|
|
|
curr_table->time_info = vmalloc(sizeof(struct time_info_list)*ip_list_tot);
|
|
if(!curr_table->time_info) {
|
|
printk(KERN_INFO RECENT_NAME ": checkentry: unable to allocate for time_info.\n");
|
|
vfree(curr_table->hash_table);
|
|
vfree(hold);
|
|
vfree(curr_table->table);
|
|
vfree(curr_table);
|
|
return 0;
|
|
}
|
|
for(c = 0; c < ip_list_tot; c++) {
|
|
curr_table->time_info[c].position = c;
|
|
curr_table->time_info[c].time = 0;
|
|
}
|
|
|
|
/* Put the new table in place */
|
|
spin_lock_bh(&recent_lock);
|
|
find_table = r_tables;
|
|
while( (last_table = find_table) && strncmp(info->name,find_table->name,IPT_RECENT_NAME_LEN) && (find_table = find_table->next) );
|
|
|
|
/* If a table already exists just increment the count on that table and return */
|
|
if(find_table) {
|
|
find_table->count++;
|
|
spin_unlock_bh(&recent_lock);
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: table found (%s), created by other process.\n",info->name);
|
|
#endif
|
|
vfree(curr_table->time_info);
|
|
vfree(curr_table->hash_table);
|
|
vfree(hold);
|
|
vfree(curr_table->table);
|
|
vfree(curr_table);
|
|
return 1;
|
|
}
|
|
if(!last_table) r_tables = curr_table; else last_table->next = curr_table;
|
|
|
|
spin_unlock_bh(&recent_lock);
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
/* Create our proc 'status' entry. */
|
|
curr_table->status_proc = create_proc_entry(curr_table->name, ip_list_perms, proc_net_ipt_recent);
|
|
if (!curr_table->status_proc) {
|
|
printk(KERN_INFO RECENT_NAME ": checkentry: unable to allocate for /proc entry.\n");
|
|
/* Destroy the created table */
|
|
spin_lock_bh(&recent_lock);
|
|
last_table = NULL;
|
|
curr_table = r_tables;
|
|
if(!curr_table) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry() create_proc failed, no tables.\n");
|
|
#endif
|
|
spin_unlock_bh(&recent_lock);
|
|
return 0;
|
|
}
|
|
while( strncmp(info->name,curr_table->name,IPT_RECENT_NAME_LEN) && (last_table = curr_table) && (curr_table = curr_table->next) );
|
|
if(!curr_table) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry() create_proc failed, table already destroyed.\n");
|
|
#endif
|
|
spin_unlock_bh(&recent_lock);
|
|
return 0;
|
|
}
|
|
if(last_table) last_table->next = curr_table->next; else r_tables = curr_table->next;
|
|
spin_unlock_bh(&recent_lock);
|
|
vfree(curr_table->time_info);
|
|
vfree(curr_table->hash_table);
|
|
vfree(hold);
|
|
vfree(curr_table->table);
|
|
vfree(curr_table);
|
|
return 0;
|
|
}
|
|
|
|
curr_table->status_proc->owner = THIS_MODULE;
|
|
curr_table->status_proc->data = curr_table;
|
|
wmb();
|
|
curr_table->status_proc->read_proc = ip_recent_get_info;
|
|
curr_table->status_proc->write_proc = ip_recent_ctrl;
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": checkentry() left.\n");
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* This function is called in the event that a rule matching this module is
|
|
* removed.
|
|
* When this happens we need to check if there are no other rules matching
|
|
* the table given. If that is the case then we remove the table and clean
|
|
* up its memory.
|
|
*/
|
|
static void
|
|
destroy(void *matchinfo, unsigned int matchsize)
|
|
{
|
|
const struct ipt_recent_info *info = matchinfo;
|
|
struct recent_ip_tables *curr_table, *last_table;
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": destroy() entered.\n");
|
|
#endif
|
|
|
|
if(matchsize != IPT_ALIGN(sizeof(struct ipt_recent_info))) return;
|
|
|
|
/* Lock the linked list while we play with it */
|
|
spin_lock_bh(&recent_lock);
|
|
|
|
/* Look for an entry with this name already created */
|
|
/* Finds the end of the list and the entry before the end if current name does not exist */
|
|
last_table = NULL;
|
|
curr_table = r_tables;
|
|
if(!curr_table) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": destroy() No tables found, leaving.\n");
|
|
#endif
|
|
spin_unlock_bh(&recent_lock);
|
|
return;
|
|
}
|
|
while( strncmp(info->name,curr_table->name,IPT_RECENT_NAME_LEN) && (last_table = curr_table) && (curr_table = curr_table->next) );
|
|
|
|
/* If a table does not exist then do nothing and return */
|
|
if(!curr_table) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": destroy() table not found, leaving.\n");
|
|
#endif
|
|
spin_unlock_bh(&recent_lock);
|
|
return;
|
|
}
|
|
|
|
curr_table->count--;
|
|
|
|
/* If count is still non-zero then there are still rules referenceing it so we do nothing */
|
|
if(curr_table->count) {
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": destroy() table found, non-zero count, leaving.\n");
|
|
#endif
|
|
spin_unlock_bh(&recent_lock);
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": destroy() table found, zero count, removing.\n");
|
|
#endif
|
|
|
|
/* Count must be zero so we remove this table from the list */
|
|
if(last_table) last_table->next = curr_table->next; else r_tables = curr_table->next;
|
|
|
|
spin_unlock_bh(&recent_lock);
|
|
|
|
/* lock to make sure any late-runners still using this after we removed it from
|
|
* the list finish up then remove everything */
|
|
spin_lock_bh(&curr_table->list_lock);
|
|
spin_unlock_bh(&curr_table->list_lock);
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
if(curr_table->status_proc) remove_proc_entry(curr_table->name,proc_net_ipt_recent);
|
|
#endif /* CONFIG_PROC_FS */
|
|
vfree(curr_table->table[0].last_pkts);
|
|
vfree(curr_table->table);
|
|
vfree(curr_table->hash_table);
|
|
vfree(curr_table->time_info);
|
|
vfree(curr_table);
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": destroy() left.\n");
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
/* This is the structure we pass to ipt_register to register our
|
|
* module with iptables.
|
|
*/
|
|
static struct ipt_match recent_match = {
|
|
.name = "recent",
|
|
.match = &match,
|
|
.checkentry = &checkentry,
|
|
.destroy = &destroy,
|
|
.me = THIS_MODULE
|
|
};
|
|
|
|
/* Kernel module initialization. */
|
|
static int __init init(void)
|
|
{
|
|
int err, count;
|
|
|
|
printk(version);
|
|
#ifdef CONFIG_PROC_FS
|
|
proc_net_ipt_recent = proc_mkdir("ipt_recent",proc_net);
|
|
if(!proc_net_ipt_recent) return -ENOMEM;
|
|
#endif
|
|
|
|
if(ip_list_hash_size && ip_list_hash_size <= ip_list_tot) {
|
|
printk(KERN_WARNING RECENT_NAME ": ip_list_hash_size too small, resetting to default.\n");
|
|
ip_list_hash_size = 0;
|
|
}
|
|
|
|
if(!ip_list_hash_size) {
|
|
ip_list_hash_size = ip_list_tot*3;
|
|
count = 2*2;
|
|
while(ip_list_hash_size > count) count = count*2;
|
|
ip_list_hash_size = count;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if(debug) printk(KERN_INFO RECENT_NAME ": ip_list_hash_size: %d\n",ip_list_hash_size);
|
|
#endif
|
|
|
|
err = ipt_register_match(&recent_match);
|
|
if (err)
|
|
remove_proc_entry("ipt_recent", proc_net);
|
|
return err;
|
|
}
|
|
|
|
/* Kernel module destruction. */
|
|
static void __exit fini(void)
|
|
{
|
|
ipt_unregister_match(&recent_match);
|
|
|
|
remove_proc_entry("ipt_recent",proc_net);
|
|
}
|
|
|
|
/* Register our module with the kernel. */
|
|
module_init(init);
|
|
module_exit(fini);
|