--- net/ipv4/netfilter/ipt_recent.c.nolimit 2006-02-15 16:34:20.000000000 +0000 +++ net/ipv4/netfilter/ipt_recent.c 2006-03-13 17:26:41.000000000 +0000 @@ -1,11 +1,12 @@ /* 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 */ +/* Copyright 2006 ufomechanic.net + for --listcount and --listtime enhancements */ /* 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 #include #include @@ -70,7 +71,11 @@ /* Structure of our linked list of tables of recent lists. */ struct recent_ip_tables { char name[IPT_RECENT_NAME_LEN]; + /* number of entries in list *table */ + int entry_count; + /* number of reference to this structure from iptables rules */ int count; + /* an index increased with each operation which maps time_info[x].position to a position in table */ int time_pos; struct recent_ip_list *table; struct recent_ip_tables *next; @@ -131,7 +136,7 @@ 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; + int len = 0, index, time, count, last_len = 0, pkt_count; off_t pos = 0; off_t begin = 0; struct recent_ip_tables *curr_table; @@ -139,22 +144,27 @@ curr_table = (struct recent_ip_tables*) data; spin_lock_bh(&curr_table->list_lock); + time=(curr_table->time_pos+ip_list_tot-1) % ip_list_tot; for(count = 0; count < ip_list_tot; count++) { - if(!curr_table->table[count].addr) continue; + /* The next slot to be re-used is the oldest slot, so start there... */ + index=curr_table->time_info[time].position; 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; } + if(curr_table->table[index].addr) { + len += sprintf(buffer+len,"src=%u.%u.%u.%u ",NIPQUAD(curr_table->table[index].addr)); + len += sprintf(buffer+len,"ttl: %u ",curr_table->table[index].ttl); + len += sprintf(buffer+len,"last_seen: %lu ",curr_table->table[index].last_seen); + len += sprintf(buffer+len,"oldest_pkt: %u ",curr_table->table[index].oldest_pkt); + len += sprintf(buffer+len,"last_pkts: %lu",curr_table->table[index].last_pkts[0]); + for(pkt_count = 1; pkt_count < ip_pkt_list_tot; pkt_count++) { + if(!curr_table->table[index].last_pkts[pkt_count]) break; + len += sprintf(buffer+len,", %lu",curr_table->table[index].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; } + } + time=(time-1+ip_list_tot) % ip_list_tot; } *start = buffer + (offset - begin); @@ -216,6 +226,7 @@ used += 5; spin_lock_bh(&curr_table->list_lock); curr_table->time_pos = 0; + curr_table->entry_count = 0; for(count = 0; count < ip_list_hash_size; count++) { curr_table->hash_table[count] = -1; } @@ -297,6 +308,9 @@ info->seconds = 0; info->hit_count = 0; info->check_set = check_set; + info->entry_count = 0; + info->entry_time = 0; + info->count_ops = 0; info->invert = 0; info->side = IPT_RECENT_SOURCE; strncpy(info->name,curr_table->name,IPT_RECENT_NAME_LEN); @@ -350,6 +364,16 @@ * --hitcount -- Option to --rcheck/--update, only match if seen hitcount times * -- matchinfo->hit_count * --seconds and --hitcount can be combined + * --listcount-lt -- Options to see if how many entries in the table altogether + * --listcount-le + * --listcount-eq + * --listcount-ge + * --listcount-gt + * --listtime-lt -- change --listcount to refer to how many entries in the table we + * updated less than so many seconds ago + * --listtime-le + * --listtime-ge + * --listtime-gt */ static int match(const struct sk_buff *skb, @@ -403,7 +427,132 @@ /* Make sure no one is changing the list while we work with it */ spin_lock_bh(&curr_table->list_lock); + /* Get jiffies now in case they changed while we were waiting for a lock */ + now = jiffies; + r_list = curr_table->table; + + /* We seek: count of items matching time constraint. + + We don't actually want to count the number of items meeting + the time constraints because it is a waste of time, we already + have an index in time order, we can easily check that there + are enough in the index. + + location = time_info[curr_table->time_pos].position; + references the next slot to be used, and which therefore is the + oldest slot to have been used if it has been used. + + start = time_info[(curr_table->time_pos + ip_list_tot -1) % ip_list_tot].position; + refers to the most recent slot to be used if any have been used + + (or if curr_table->entry_count > 0) + end = time_info[(curr_table->time_pos + ip_list_tot + - curr_table->entry_count) % ip_list_tot ].position; + will always show the oldest item if entry_count > 0 + + SO, if we want to check that there are n items in the list that + are < or <= to t then we go to position (END+n-1) and compare that. + If it is < or <= t then so are the old items + If we want to check that there are n items > or >= t then we + check item START-n+1 and compare that, if it is < or <= t then so + are the items after it. + + Here are the full cases we cover. We don't do time= because its not + very useful and difficult to optimize + + time > t, count > c : itemcount > c && [start-c]>t ; there are enough, and past the limitmatches + time > t, count >= c : itemcount >= c && [start-c+1]>t ; there are enough and the limit matches + time > t, count = c : itemcount >= c && [start-c+1]>t ; there are at least enough and the limit matches + && (c<=itemcount || !([start-c]>t)) ; others, if any, do not match + time > t, count <= c : itemcount <= c || !([start-c]>t) ; either there aren't too many anyway, or the one just out of count doesn't match + time > t, count < c : itemcount < c || !([start-c+1]>t) ; either there aren't too many anyway, or the one just out of count doesn't match + + + time >= t, count > c : itemcount > c && [start-c]>=t ; there are enough past the limit + time >= t, count >= c : itemcount >=c && [start-c+1]>=t ; there are enough and the limit matches + time >= t, count = c : itemcount >=c && [start-c+1]>=t ; there are at least enough and the limit matches + && (c<=itemcount || !([start-c]>=t)) ; others, if any, do not match + time >= t, count <= c : itemcount <=c || !([start-c]>=t) ; either there aren't too many anyway, or the one just out of count doesn't match + time >= t, count < c : itemcount < c || !([start-c+1]>=t) ; either there aren't too many anyway, or the one just out of count doesn't match + + + time <= t, count > c : itemcount > c && [end+c]<=t ; Make sure that at least 1 more than c matches + time <= t, count >= c : itemcount >= c && [end+c-1]<=t ; Make sure that at least c items match + time <= t, count = c : itemcount >= c && [end+c-1]<=t ; there are at least enough and the limit matches + && (c<=itemcount) || !([end+c]<=t)) ; others, if any, do not match + time <= t, count <= c : itemcount <=c || !([end+c]<=t) ; either there aren't too many anyway, or the one out of count doesn't match + time <= t, count < c : itemcount < c || !([end+c-1]<=t) ; either there aren't too many anyway, or the one out of count doesn't match + + + time < t, count > c : itemcount > c && [end+c]= c : itemcount >= c && [end+c-1]= c && [end+c-1]= c && ([end+c-1]entry_count COUNT_CHECK \ + ((!timecheck) || TIME_OP(now,tablelist->time_info[(limit+ip_list_tot)%ip_list_tot].time+(unsigned long)(timecheck)*HZ))) ) + + #define COUNT_GT(table,timecheck,TIME_OP,countcheck,limit,DIR_OP) COUNT_(table,timecheck,TIME_OP, > countcheck &&,limit DIR_OP countcheck) + #define COUNT_GE(table,timecheck,TIME_OP,countcheck,limit,DIR_OP) COUNT_(table,timecheck,TIME_OP,>= countcheck &&,limit DIR_OP countcheck - DIR_OP 1) + #define COUNT_EQ(table,timecheck,TIME_OP,countcheck,limit,DIR_OP) COUNT_GE(table,timecheck,TIME_OP,countcheck,limit,DIR_OP) \ + && ! COUNT_GT(table,timecheck,TIME_OP,countcheck,limit,DIR_OP) + #define COUNT_LE(table,timecheck,TIME_OP,countcheck,limit,DIR_OP) COUNT_(table,timecheck,TIME_OP,<= countcheck || !,limit DIR_OP countcheck) + #define COUNT_LT(table,timecheck,TIME_OP,countcheck,limit,DIR_OP) COUNT_(table,timecheck,TIME_OP, < countcheck || !,limit DIR_OP countcheck - DIR_OP 1) + + /* and one rule to bind them. Macros were made for this. + Inline functions won't be efficient here because we would have to pass a function pointer or something */ + #define COUNT(COUNT_FUNC,TIME_OP,table,timecheck,countcheck) (\ + (((TIME_OP)==IPT_RECENT_TIME_GT)?(COUNT_FUNC(table,timecheck,time_after, countcheck,table->time_pos-table->entry_count,+)):\ + ((TIME_OP)==IPT_RECENT_TIME_GE)?(COUNT_FUNC(table,timecheck,time_after_eq, countcheck,table->time_pos-table->entry_count,+)):\ + ((TIME_OP)==IPT_RECENT_TIME_LE)?(COUNT_FUNC(table,timecheck,time_before_eq, countcheck,table->time_pos-1,-)):\ + ((TIME_OP)==IPT_RECENT_TIME_LT)?(COUNT_FUNC(table,timecheck,time_before, countcheck,table->time_pos-1,-)):\ + ((TIME_OP)==0 )?(COUNT_FUNC(table,0, time_after, countcheck,table->time_pos-1,-)):\ + 0) \ + ) + + /* we use do..while(0) so that the acceptable cases can "break" away from the + exit block which saves repeating the exit block for each case */ + if(info->count_ops) do { + int invert=(0!=(info->count_ops & IPT_RECENT_COUNT_INVERT)); +#ifdef DEBUG + if (debug) printk(KERN_INFO RECENT_NAME " match invert=%d count_ops=%x count=%d time=%d\n",invert,info->count_ops,info->entry_count,info->entry_time); +#endif + + /* of course limit and over_limit may be invalid if ccount_ops & IPT_RECENT_COUNT_OPS)==IPT_RECENT_COUNT_LT) { + if (invert ^ COUNT(COUNT_LT,info->count_ops & IPT_RECENT_TIME_OPS,curr_table,info->entry_time,info->entry_count)) break; + } else if ((info->count_ops & IPT_RECENT_COUNT_OPS)==IPT_RECENT_COUNT_LE) { + if (invert ^ COUNT(COUNT_LE,info->count_ops & IPT_RECENT_TIME_OPS,curr_table,info->entry_time,info->entry_count)) break; + } else if ((info->count_ops & IPT_RECENT_COUNT_OPS)==IPT_RECENT_COUNT_EQ) { + if (invert ^ COUNT(COUNT_EQ,info->count_ops & IPT_RECENT_TIME_OPS,curr_table,info->entry_time,info->entry_count)) break; + } else if ((info->count_ops & IPT_RECENT_COUNT_OPS)== IPT_RECENT_COUNT_GE) { + if (invert ^ COUNT(COUNT_GE,info->count_ops & IPT_RECENT_TIME_OPS,curr_table,info->entry_time,info->entry_count)) break; + } else if ((info->count_ops & IPT_RECENT_COUNT_OPS)== IPT_RECENT_COUNT_GT) { + if (invert ^ COUNT(COUNT_GT,info->count_ops & IPT_RECENT_TIME_OPS,curr_table,info->entry_time,info->entry_count)) break; + } + /* If we get here then a definite comparison failed */ + spin_unlock_bh(&curr_table->list_lock); + return 0; + } while(0); + + /* this is the next slot to use. + If it has never been used then 0 is the oldest slot + If it has been used then it is the oldest slot. */ + location = time_info[curr_table->time_pos].position; + + if(info->side == IPT_RECENT_DEST) addr = skb->nh.iph->daddr; else addr = skb->nh.iph->saddr; if(!addr) { @@ -418,8 +567,6 @@ 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; @@ -500,7 +647,27 @@ /* 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; + /* + location is the next slot in r_list that we will use. + Maybe it is already used and we need to remove it from the hash. + If it is used then: + 1) hash_table[r_list[location].hash_entry]=location + Which is to say that anything claiming to have a hash entry + should also be pointed to BY that hash entry. + This only comes up becase by default r_list[x].hash_entry=0 + i.e. all items claim to have hash entry 0, so if a real + item has hash slot 0 it gets trashed because of lies. + SO: If r_list[location].hash_entry]==0 we are in warning condition + and need to double check that hash_table reciprocates before we + set the hash_table entry to -1 (it may not really belong) + This would work; + if (r_list[location].hash_entry!=0 || hash_table[0]==location) hash_table[r_list[location].hash_entry] = -1; + but we want to combine with a counter for new entries + */ + /* increase counter if this is a new item */ + if (hash_table[r_list[location].hash_entry]!=location) curr_table->entry_count++; + else 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(u_int32_t)); r_list[location].time_pos = curr_table->time_pos; @@ -587,7 +754,7 @@ #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++; + orig_hash_result = (orig_hash_result+1) % ip_list_hash_size; 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 @@ -616,7 +783,10 @@ 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) { + /* close up the gap we made in time_info. + Perhaps we should work out which is the quickest way around the ring buffer + and close up the gap the quickest way. We should use memmove while we are at it. */ + while(((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; @@ -633,6 +803,9 @@ r_list[location].ttl = 0; memset(r_list[location].last_pkts,0,ip_pkt_list_tot*sizeof(u_int32_t)); r_list[location].oldest_pkt = 0; + curr_table->entry_count--; + /* back off next-free-slot time_pos to the free slot we just bubbled to the end */ + curr_table->time_pos=(curr_table->time_pos+ip_list_tot-1)%ip_list_tot; ans = !info->invert; } spin_unlock_bh(&curr_table->list_lock); @@ -669,6 +842,11 @@ if (matchsize != IPT_ALIGN(sizeof(struct ipt_recent_info))) return 0; + if(! (info->check_set & IPT_RECENT_HAS_LIST_OPS)) { + printk(KERN_INFO RECENT_NAME ": supports LIST_OPS but user iptables does not!\n"); + 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; } @@ -719,6 +897,7 @@ curr_table->next = NULL; curr_table->count = 1; curr_table->time_pos = 0; + curr_table->entry_count = 0; strncpy(curr_table->name,info->name,IPT_RECENT_NAME_LEN); curr_table->name[IPT_RECENT_NAME_LEN-1] = '\0'; --- include/linux/netfilter_ipv4/ipt_recent.h.nolimit 2006-02-20 10:12:06.000000000 +0000 +++ include/linux/netfilter_ipv4/ipt_recent.h 2006-03-13 17:11:08.000000000 +0000 @@ -2,13 +2,31 @@ #define _IPT_RECENT_H #define RECENT_NAME "ipt_recent" -#define RECENT_VER "v0.3.1" +#define RECENT_VER "v0.3.2" #define IPT_RECENT_CHECK 1 #define IPT_RECENT_SET 2 #define IPT_RECENT_UPDATE 4 #define IPT_RECENT_REMOVE 8 #define IPT_RECENT_TTL 16 +#define IPT_RECENT_HAS_LIST_OPS 32 + +#ifdef IPT_RECENT_HAS_LIST_OPS +#define IPT_RECENT_COUNT_INVERT 1 << 0 +#define IPT_RECENT_COUNT_LT 1 << 1 +#define IPT_RECENT_COUNT_EQ 1 << 2 +#define IPT_RECENT_COUNT_GT 1 << 3 +#define IPT_RECENT_COUNT_LE (IPT_RECENT_COUNT_LT | IPT_RECENT_COUNT_EQ) +#define IPT_RECENT_COUNT_GE (IPT_RECENT_COUNT_GT | IPT_RECENT_COUNT_EQ) +#define IPT_RECENT_COUNT_OPS (IPT_RECENT_COUNT_LT | IPT_RECENT_COUNT_EQ | IPT_RECENT_COUNT_GT) + +#define IPT_RECENT_TIME_LT 1 << 4 +#define IPT_RECENT_TIME_EQ 1 << 5 /* But we don't implement checks for == itself */ +#define IPT_RECENT_TIME_GT 1 << 6 +#define IPT_RECENT_TIME_LE (IPT_RECENT_TIME_LT | IPT_RECENT_TIME_EQ) +#define IPT_RECENT_TIME_GE (IPT_RECENT_TIME_GT | IPT_RECENT_TIME_EQ) +#define IPT_RECENT_TIME_OPS (IPT_RECENT_TIME_LT | IPT_RECENT_TIME_EQ | IPT_RECENT_TIME_GT) +#endif #define IPT_RECENT_SOURCE 0 #define IPT_RECENT_DEST 1 @@ -20,6 +38,11 @@ u_int32_t hit_count; u_int8_t check_set; u_int8_t invert; +#ifdef IPT_RECENT_HAS_LIST_OPS + u_int8_t count_ops; + u_int32_t entry_count; + u_int32_t entry_time; +#endif char name[IPT_RECENT_NAME_LEN]; u_int8_t side; };