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-yo,
--
--------------------------------------------------
| 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
#define TCPCB_FLAG_SYN 0x02
#define TCPCB_FLAG_RST 0x04
#define TCPCB_FLAG_PSH 0x08
#define TCPCB_FLAG_ACK 0x10
#define TCPCB_FLAG_URG 0x20
#define TCPCB_FLAG_ECE 0x40
#define TCPCB_FLAG_CWR 0x80
#define REQUIRED_SYNS_CEIL 10
#define HERTZ 1000
MODULE_AUTHOR("Rick Blundell");
MODULE_LICENSE("RJB");
MODULE_DESCRIPTION("netfilter syn attack evade");
MODULE_SUPPORTED_DEVICE("none");
struct timer_list my_timer;
static struct proc_dir_entry *procentry;
static struct authorizedsrc {
struct authorizedsrc *next;
__be32 ip;
int timestamp;
int syn_count;
};
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;
int timeout=1;
int required_syns=5;
/* FXN DEFINITIONS */
struct authorizedsrc * 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);
static void my_timer_func(struct authorizedsrc **head);
static struct proc_dir_entry *proc_authorizedsrcs;
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);
static struct proc_dir_entry *procentry_timeout;
int write_timeout_proc(struct file *file, const char __user *buffer, unsigned long count, void *data);
int read_timeout_proc(char *buffer, char **start, off_t offset, int count, int *peof, void *dat);
static struct proc_dir_entry *procentry_required_syns;
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);
__be32 getip(const char* buff);
/* init FXN! */
static int __init mod_init(void) {
nf_register_hook(&hook_in);
procentry_auth = create_proc_entry("auth", 0644, proc_net_netfilter_synevade);
if (procentry_auth) {
procentry_auth->read_proc = read_proc_auth;
procentry_auth->data = NULL;
}
procentry_auth_pending = create_proc_entry("auth_pending", 0644, proc_net_netfilter);
if (procentry_auth_pending) {
procentry->read_proc = read_proc_auth_pending;
procentry->data = NULL;
}
procentry_timeout=create_proc_entry("src_timeout", 0644, proc_net_netfilter);
if (procentry_timeout){
procentry_timeout->write_proc=write_timeout_proc;
procentry_timeout->read_proc=read_timeout_proc;
procentry_timeout->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;
}
printk("synattack_evader loaded\n");
init_timer(&my_timer);
my_timer.function = my_timer_func;
my_timer.data = (struct authorized_src *) head_auth_pending;
my_timer.expires = jiffies + HERTZ*timeout;
add_timer(&my_timer);
return 0;
}
/* EXIT AND CLEANUP! */
static void __exit mod_exit(void) {
del_timer(&my_timer);
nf_unregister_hook(&hook_in);
remove_proc_entry("authorized_srcs", proc_net_netfilter);
remove_proc_entry("src_timeout", proc_net_netfilter);
remove_proc_entry("required_syns", proc_net_netfilter);
printk("synattack_evader unloaded\n");
}
/* ADD THEM TO KERNEL! */
module_init( mod_init );
module_exit( mod_exit );
/* RUNS On EACH PACKET */
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;
struct hostentry *host;
__be32 src_ip = 0;
struct tcp_skb_cb *tcb = TCP_SKB_CB(buff);
switch (hooknum) {
/* INPUT TABLE PACKET */
case NF_IP_LOCAL_IN:
//Address of client
src_ip=buff->nh.iph->saddr;
//printk("source is %u.%u.%u.%u\n",NIPQUAD(src_ip));
//CHECK IF THIS PACKET IS A SYN
if (tcb->flags = TCPCB_FLAG_SYN) {
//printk("syn is %u.%u.%u.%u\n",NIPQUAD(src_ip));
//check if in authorized_src list
if( authorized_src(&head_auth,src_ip) ){
//allow
}else{
//deny and add to list
//rc=NF_DROP;
printk("dropping syn from %u.%u.%u.%u\n",NIPQUAD(src_ip));
authorize_src(&head_auth_pending,&head_auth,src_ip,required_syns);
}
}
break;
}
return rc;
}
/* Check if the source address is in the table "required" times */
int authorized_src( struct authorizedsrc **head,__be32 src_ip){
struct authorizedsrc *tmp=*head;
while(tmp!=NULL){
//printk("checking if \"%u.%u.%u.%u\" = \"%u.%u.%u.%u\" \n",NIPQUAD(tmp->ip),NIPQUAD(src_ip));
if(tmp->ip==src_ip){
//printk("\tauthoized!..\n");
return 1;
}
tmp=tmp->next;
}
//printk("\tnot authorized..\n");
return 0;
}
/* Run cleanup on the list */
void free_authorized_src(struct authorizedsrc *head){
struct authorizedsrc *tmp=head;
while(head!=NULL){
tmp=head->next;
kfree(head);
head=tmp;
}
}
/* 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.
//find it in the list
struct authorizedsrc *tmp;
tmp=*head_auth_pending;
int count=0;
while(tmp!=NULL){
if(src_ip == tmp->ip){
//found it!
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->syn_count=1;
printk("added ip %u.%u.%u.%u to authed!\n",NIPQUAD(src_ip));
tmp->next=*head_auth;
*head_auth=tmp;
}
//remov it from head_auth_pending...
remove_ip_from_list(head_auth_pending,src_ip);
return 1;
}else{
printk("incrementing syn_count on ip %u.%u.%u.%u\n",NIPQUAD(src_ip));
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;
struct timeval tv;
do_gettimeofday(&tv);
tmp->timestamp=tv.tv_sec;
tmp->syn_count=1;
printk("added ip %u.%u.%u.%u\n",NIPQUAD(src_ip));
tmp->next=*head_auth_pending;
*head_auth_pending=tmp;
}
return 1;
}
}
struct authorizedsrc * remove_ip_from_list(struct authorizedsrc *head,__be32 ip){
if(head==NULL) return NULL;
if(head->ip==ip){
struct authorizedsrc * save;
save=head->next;
kfree(head);
return save;
}
head->next=remove_ip_from_list(head->next,ip);
return head;
}
int read_proc_auth_pending(char *buff, char **start, off_t offset, int count, int *peof, void *dat) {
int rc=0;
struct authorizedsrc *tmp=head_auth_pending;
while(tmp!=NULL){
if(tmp==NULL){*peof=1;return rc;}
rc += snprintf (buff+rc, count-rc, "%u.%u.%u.%u:%d:%d\n",NIPQUAD(tmp->ip),tmp->syn_count,tmp->timestamp);
tmp=tmp->next;
}
*peof=1;
return rc;
}
int read_proc_auth(char *buff, char **start, off_t offset, int count, int *peof, void *dat) {
int rc=0;
struct authorizedsrc *tmp=head_auth;
while(tmp!=NULL){
if(tmp==NULL){*peof=1;return rc;}
rc += snprintf (buff+rc, count-rc, "%u.%u.%u.%u:%d:%d\n",NIPQUAD(tmp->ip),tmp->syn_count,tmp->timestamp);
tmp=tmp->next;
}
*peof=1;
return rc;
}
int read_timeout_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",timeout);
*peof=1;
return rc;
}
int write_timeout_proc(struct file *file, const char __user *buffer, unsigned long count, void *data) {
char *buff;
int rc;
buff = kmalloc(count+1, GFP_KERNEL);
if(buff) {
rc = copy_from_user(buff, buffer, count);
timeout=simple_strtol(buff,NULL,0);
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){
printk("reaping..\n");
// free_authorized_src(head);
my_timer.expires = jiffies + HERTZ*timeout;
add_timer(&my_timer);
}