Sorry, I attached the wrong version. Attached is the latest version.
Rick Blundell wrote:
Hello. I wrote this netfilter module for Linux 2.6, its an experiment to
try and mitigate the effect of a spoofed syn attack on a server, and
also to learn more about the netfilter hooks.
With this module loaded, all syn packets are distrusted. My idea was to
to mitigate the effect of a spoofed syn attack by discarding all initial
SYN packets from a given client address, and permit SYN packets which
arrive from client addresses which were recently discarded. So, we have
two lists- trusted and pending trusted. The trusted list is full of
client addresses which reconnected after being discarded. This is
similar to email greylisting. Both of the lists are periodically
cleaned up and there are some tunable values such as the number of
necessary syn reconnects to become trusted and how often to clean each
list.
Looking for any feedback on my overall idea or the code implementation.
My module code is attached.
Thank-you,
--
--------------------------------------------------
| Rick J. Blundell |
| <rickb@xxxxxxxxxxxx> |
--------------------------------------------------
| Mac OS X proves that it's easier to make UNIX |
| pretty than it is to make Windows secure. |
--------------------------------------------------
#include <linux/module.h>
#include <linux/init.h>
#include <linux/stat.h> // for proc permissions
#include <linux/netdevice.h> // struct net_device, net_device functions
#include <linux/skbuff.h> // struct sk_buff
#include <linux/ip.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/string.h>
#include <linux/time.h>
#include <net/tcp.h>
#include <linux/time.h>
#define TCPCB_FLAG_FIN 0x01
// flag for syn packet
#define REQUIRED_SYNS_CEIL 10
// max syns to require auth.
#define REAPER_FREQ 100
// 10 times per second. pending auth cleanup function runs this often
#define REAPER_2_FREQ 60000
// 1 time per minute. auth cleanup function runs this often
#define PENDING_TIMEOUT_MS_FLOOR 500
// minimum frequency for pending_timeout_ms. 500ms = 2 times per sec
#define PENDING_TIMEOUT_MS_CEILING 60000
// maximum frequenct for pending_timeout_ms. 60000ms = 1 times per minute
#define AUTH_TIMEOUT_SEC_FLOOR 1
// minumum seconds to hold address in auth table
#define AUTH_TIMEOUT_SEC_CEILING 3600
// minumum seconds to hold address in auth table
MODULE_AUTHOR("Rick Blundell");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("netfilter syn attack evade");
MODULE_SUPPORTED_DEVICE("none");
struct timer_list my_timer;
struct timer_list my_timer2;
static struct proc_dir_entry *procentry_auth;
static struct proc_dir_entry *procentry_auth_pending;
static struct authorizedsrc {
struct authorizedsrc *next;
__be32 ip;
int timestamp;
int timestamp_usec;
int syn_count;
spinlock_t list_lock;
};
unsigned int hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *));
static struct nf_hook_ops hook_in = {
.list = {NULL, NULL},
.hook = hookfn,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_IN
};
struct authorizedsrc *head_auth_pending=NULL;
struct authorizedsrc *head_auth=NULL;
static struct proc_dir_entry *proc_authorizedsrcs;
static struct proc_dir_entry *procentry_timeout_ms;
static struct proc_dir_entry *procentry_timeout_sec;
static struct proc_dir_entry *procentry_required_syns;
int required_syns=2;
int pending_timeout_ms=5000;
int auth_timeout_sec=5*60;
/* FXN DEFINITIONS */
void remove_ip_from_list(struct authorizedsrc **head,__be32 ip);
int authoized_src(struct authorizedsrc **head,__be32 src_ip);
void authorize_src(struct authorizedsrc **head,struct authorizedsrc **head2,__be32 src_ip, int required_syns);
void free_authorized_src(struct authorizedsrc **head);
void free_authorized_src2(struct authorizedsrc **head);
static void my_timer_func(struct authorizedsrc **head);
static void my_timer_func2(struct authorizedsrc **head);
int read_proc_auth_pending(char *buffer, char **start, off_t offset, int count, int *peof, void **dat);
int read_proc_auth(char *buffer, char **start, off_t offset, int count, int *peof, void **dat);
int write_required_syns_proc(struct file *file, const char __user *buffer, unsigned long count, void *data);
int read_required_syns_proc(char *buffer, char **start, off_t offset, int count, int *peof, void *dat);
int write_pending_timeout_ms_proc(struct file *file, const char __user *buffer, unsigned long count, void *data);
int read_pending_timeout_ms_proc(char *buffer, char **start, off_t offset, int count, int *peof, void *dat);
int write_auth_timeout_sec_proc(struct file *file, const char __user *buffer, unsigned long count, void *data);
int read_auth_timeout_sec_proc(char *buffer, char **start, off_t offset, int count, int *peof, void *dat);
static int __init mod_init(void) {
procentry_auth = create_proc_entry("auth", 0644, proc_net_netfilter);
if (procentry_auth) {
procentry_auth->read_proc = read_proc_auth;
procentry_auth->data = &head_auth;
}
procentry_auth_pending = create_proc_entry("auth_pending", 0644, proc_net_netfilter);
if (procentry_auth_pending) {
procentry_auth_pending->read_proc = read_proc_auth_pending;
procentry_auth_pending->data = &head_auth_pending;
}
procentry_timeout_ms=create_proc_entry("pending_timeout_ms", 0644, proc_net_netfilter);
if (procentry_timeout_ms){
procentry_timeout_ms->write_proc=write_pending_timeout_ms_proc;
procentry_timeout_ms->read_proc=read_pending_timeout_ms_proc;
procentry_timeout_ms->data=NULL;
}
procentry_timeout_sec=create_proc_entry("auth_timeout_sec", 0644, proc_net_netfilter);
if (procentry_timeout_sec){
procentry_timeout_sec->write_proc=write_auth_timeout_sec_proc;
procentry_timeout_sec->read_proc=read_auth_timeout_sec_proc;
procentry_timeout_sec->data=NULL;
}
procentry_required_syns=create_proc_entry("required_syns", 0644, proc_net_netfilter);
if(procentry_required_syns){
procentry_required_syns->write_proc=write_required_syns_proc;
procentry_required_syns->read_proc=read_required_syns_proc;
procentry_required_syns->data=NULL;
}
nf_register_hook(&hook_in);
printk("synattack_evader loaded\n");
/* This timer cleans the auth pending list */
init_timer(&my_timer);
my_timer.function = my_timer_func;
my_timer.data = (struct authorized_src **) &head_auth_pending;
my_timer.expires = jiffies + REAPER_FREQ;
add_timer(&my_timer);
/* This timer cleans the auth list */
init_timer(&my_timer2);
my_timer2.function = my_timer_func2;
my_timer2.data = (struct authorized_src **) &head_auth;
my_timer2.expires = jiffies + REAPER_2_FREQ;
add_timer(&my_timer2);
return 0;
}
static void __exit mod_exit(void) {
del_timer(&my_timer);
del_timer(&my_timer2);
nf_unregister_hook(&hook_in);
remove_proc_entry("required_syns", proc_net_netfilter);
remove_proc_entry("auth", proc_net_netfilter);
remove_proc_entry("auth_pending", proc_net_netfilter);
remove_proc_entry("pending_timeout_ms",proc_net_netfilter);
remove_proc_entry("auth_timeout_sec",proc_net_netfilter);
printk("synattack_evader unloaded\n");
}
module_init( mod_init );
module_exit( mod_exit );
unsigned int hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *)) {
/* DEFAULT POLICY IS ACCEPT THE PACKET */
unsigned int rc = NF_ACCEPT;
struct sk_buff *buff = *skb;
__be32 src_ip = 0;
struct tcp_skb_cb *tcb = TCP_SKB_CB(buff);
switch (hooknum) {
/* INPUT TABLE PACKET */
case NF_IP_LOCAL_IN:
src_ip=buff->nh.iph->saddr;
//CHECK IF THIS PACKET IS A SYN
if (tcb->flags = TCPCB_FLAG_SYN) {
//check if in auth authorized_src list
if( authorized_src(&head_auth,src_ip) ){
//allow this packet. the client is authorized
}else{
//deny packet and add to list
rc=NF_DROP;
spin_lock_bh(head_auth_pending->list_lock);
spin_lock_bh(head_auth->list_lock);
authorize_src(&head_auth_pending,&head_auth,src_ip,required_syns);
spin_unlock_bh(head_auth_pending->list_lock);
spin_unlock_bh(head_auth->list_lock);
}
}
break;
}
return rc;
}
/* Check if the address is in the table */
int authorized_src( struct authorizedsrc **head,__be32 src_ip){
struct authorizedsrc *tmp=*head;
while(tmp!=NULL){
if(tmp->ip==src_ip){
return 1;
}
tmp=tmp->next;
}
return 0;
}
void free_authorized_src(struct authorizedsrc **head){
unsigned int ts=0, expire;
struct timeval tv;
do_gettimeofday(&tv);
struct authorizedsrc *tmp;
tmp=*head;
for(tmp=*head;tmp!=NULL;tmp=tmp->next){
if( tv.tv_sec > tmp->timestamp+(pending_timeout_ms/1000) ){
// the element is older by more then a second
spin_lock_bh(tmp->list_lock);
remove_ip_from_list(head,tmp->ip);
spin_unlock_bh(tmp->list_lock);
}else if( tv.tv_sec == tmp->timestamp+(pending_timeout_ms/1000) && tv.tv_usec > (tmp->timestamp_usec+ (pending_timeout_ms*1000) )) {
// the element is older by more then a fraction of a second
spin_lock_bh(tmp->list_lock);
remove_ip_from_list(head,tmp->ip);
spin_unlock_bh(tmp->list_lock);
}else{
// the element has not expired yet. leave it in the list.
// printk("[CLEANUP3] NOT removing \"%u.%u.%u.%u\". packet_usec=\"%d\", packet_sec=\"%d\" tsu=\"%d\", now_usec=\"%d\" now_sec=\"%d\"\n",
// NIPQUAD(tmp->ip), tmp->timestamp_usec,tmp->timestamp,pending_timeout_ms/1000,tv.tv_usec,tv.tv_sec);
}
}
}
void free_authorized_src2(struct authorizedsrc **head){
unsigned int ts=0;
struct timeval tv;
do_gettimeofday(&tv);
struct authorizedsrc *tmp;
tmp=*head;
for(tmp=*head;tmp!=NULL;tmp=tmp->next){
if( tv.tv_sec > (tmp->timestamp)+auth_timeout_sec ){
// the element is older by more then a second
spin_lock_bh(tmp->list_lock);
remove_ip_from_list(head,tmp->ip);
spin_unlock_bh(tmp->list_lock);
}else{
// the element has not expired yet. leave it in the list.
}
}
}
/* add src_ip to auth list or increment syn_count if already in the list */
void authorize_src(struct authorizedsrc **head_auth_pending, struct authorizedsrc **head_auth,__be32 src_ip, int required_syns){
//printk("in authorize_src..\n");
if(authorized_src(head_auth_pending,src_ip)){
//printk("checking authorized in authorize.\n");
//already in the list. we need to increment syn_count or move it to the auth list if syn_count >= required-1
struct authorizedsrc *tmp;
tmp=*head_auth_pending;
while(tmp!=NULL){
if(src_ip == tmp->ip){
if(tmp->syn_count >= required_syns-1){
//add to head_auth and remove from head_auth_pending
struct authorizedsrc *tmp;
if ((tmp = kmalloc(sizeof(*tmp), GFP_KERNEL)) ) {
tmp->ip=src_ip;
struct timeval tv;
do_gettimeofday(&tv);
tmp->timestamp=tv.tv_sec;
tmp->timestamp_usec=tv.tv_usec;
tmp->syn_count=1;
tmp->next=*head_auth;
*head_auth=tmp;
}
// remove it from head_auth_pending...
remove_ip_from_list(head_auth_pending,src_ip);
return 1;
}else{
// increment syn_count for this element
tmp->syn_count++;
}
return 0;
}
tmp=tmp->next;
}
return 1;
}else{
//not in the list. insert it into the list
struct authorizedsrc *tmp;
if ((tmp = kmalloc(sizeof(*tmp), GFP_KERNEL)) ) {
tmp->ip=src_ip;
tmp->next=NULL;
struct timeval tv;
do_gettimeofday(&tv);
tmp->timestamp=tv.tv_sec;
tmp->timestamp_usec=tv.tv_usec;
tmp->syn_count=1;
//printk("added ip %u.%u.%u.%u to pending\n",NIPQUAD(src_ip));
tmp->next=*head_auth_pending;
*head_auth_pending=tmp;
}
return 1;
}
}
void remove_ip_from_list(struct authorizedsrc **head,__be32 ip){
struct authorizedsrc *currP, *prevP;
prevP = NULL;
for (currP = *head;
currP != NULL;
prevP = currP, currP = currP->next) {
if(currP->ip==ip){
if(prevP == NULL){
*head=currP->next;
}else{
prevP->next=currP->next;
}
kfree(currP);
return;
}
}
return;
}
int read_proc_auth_pending(char *buff, char **start, off_t offset, int count, int *peof, void **dat) {
int rc=0;
int last_len = 0;
off_t pos = 0;
off_t begin = 0;
struct authorizedsrc *tmp=(struct authorizedsrc *)*dat;
spin_lock_bh(tmp->list_lock);
while(tmp!=NULL){
last_len=rc;
rc += sprintf (buff+rc, "%u.%u.%u.%u:%d\n",NIPQUAD(tmp->ip),tmp->syn_count);
pos=begin+rc;
if(pos < offset) { rc = 0; begin = pos; }
if(pos > offset + count) { rc = last_len; break; }
tmp=tmp->next;
}
*start=buff+(offset-begin);
rc-=(offset-begin);
if(rc > count) rc = count;
spin_unlock_bh(tmp->list_lock);
return rc;
}
int read_proc_auth(char *buff, char **start, off_t offset, int count, int *peof, void **dat) {
int rc=0;
int last_len = 0;
off_t pos = 0;
off_t begin = 0;
struct authorizedsrc *tmp=(struct authorizedsrc *)*dat;
spin_lock_bh(tmp->list_lock);
while(tmp!=NULL){
last_len=rc;
rc += snprintf (buff+rc, count-rc, "%u.%u.%u.%u:%d\n",NIPQUAD(tmp->ip),tmp->timestamp);
pos=begin+rc;
if(pos < offset) { rc = 0; begin = pos; }
if(pos > offset + count) { rc = last_len; break; }
tmp=tmp->next;
}
*start=buff+(offset-begin);
rc-=(offset-begin);
if(rc > count) rc = count;
spin_unlock_bh(tmp->list_lock);
return rc;
}
int read_pending_timeout_ms_proc(char *buff, char **start, off_t offset, int count, int *peof, void *data) {
int rc=0;
rc+=snprintf(buff+rc,count-rc,"%d\n",pending_timeout_ms);
*peof=1;
return rc;
}
int write_pending_timeout_ms_proc(struct file *file, const char __user *buffer, unsigned long count, void *data) {
char *buff;
int rc, test_int=0;
buff = kmalloc(count+1, GFP_KERNEL);
if(buff) {
rc = copy_from_user(buff, buffer, count);
test_int=simple_strtol(buff,NULL,0);
if(test_int >= PENDING_TIMEOUT_MS_FLOOR && test_int <= PENDING_TIMEOUT_MS_CEILING){
pending_timeout_ms=test_int;
}
kfree(buff);
}else {
return -ENOMEM;
}
return count - rc;
}
int read_auth_timeout_sec_proc(char *buff, char **start, off_t offset, int count, int *peof, void *data) {
int rc=0;
rc+=snprintf(buff+rc,count-rc,"%d\n",auth_timeout_sec);
*peof=1;
return rc;
}
int write_auth_timeout_sec_proc(struct file *file, const char __user *buffer, unsigned long count, void *data) {
char *buff;
int rc, test_int=0;
buff = kmalloc(count+1, GFP_KERNEL);
if(buff) {
rc = copy_from_user(buff, buffer, count);
test_int=simple_strtol(buff,NULL,0);
if(test_int >= AUTH_TIMEOUT_SEC_FLOOR && test_int <= AUTH_TIMEOUT_SEC_CEILING){
auth_timeout_sec=test_int;
}
kfree(buff);
}else {
return -ENOMEM;
}
return count - rc;
}
int read_required_syns_proc(char *buff, char **start, off_t offset, int count, int *peof, void *dat) {
int rc=0;
rc+=snprintf(buff+rc,count-rc,"%d\n",required_syns);
*peof=1;
return rc;
}
int write_required_syns_proc(struct file *file, const char __user *buffer, unsigned long count, void *data) {
char *buff;
int rc;
int tmp_rs;
buff = kmalloc(count+1, GFP_KERNEL);
if(buff) {
rc = copy_from_user(buff, buffer, count);
tmp_rs=simple_strtol(buff,NULL,0);
if(tmp_rs <= REQUIRED_SYNS_CEIL) required_syns=tmp_rs;
kfree(buff);
}else {
return -ENOMEM;
}
return count - rc;
}
static void my_timer_func(struct authorizedsrc **head){
free_authorized_src(head);
my_timer.expires = jiffies + REAPER_FREQ;
add_timer(&my_timer);
}
static void my_timer_func2(struct authorizedsrc **head){
free_authorized_src2(head);
my_timer.expires = jiffies + REAPER_2_FREQ;
add_timer(&my_timer2);
}