#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/init.h>
#include <linux/tcp.h>
#include <asm/io.h>
#include <linux/inet.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
#include <linux/in.h>
#include <linux/ip.h>
#endif

#define RING_SIZE 500
#define SYNC_RANGE 100
MODULE_LICENSE("GPL");

static wait_queue_head_t wq;
static int thread_id;
static DECLARE_COMPLETION(on_exit);

static struct nf_hook_ops nfho_out,nfho_in;

u16 tone[RING_SIZE];
int tone_counter;
int tone_pos_counter;

void update_tone_pos_counter(void) {
  if(tone_pos_counter<RING_SIZE-1) tone_pos_counter++;
  else tone_pos_counter=0;
}

void update_tone_counter(void) {
  if(tone_counter<RING_SIZE-1) tone_counter++;
  else tone_counter=0;
}
    
void check_syncing(void) {
  int counter;
  if ((abs((tone_pos_counter - tone_counter+RING_SIZE)
           % RING_SIZE))>SYNC_RANGE)
  {
    counter=tone_counter;

    while (((abs((counter - tone_counter+RING_SIZE)
                 % RING_SIZE))<=SYNC_RANGE))
    {
      tone[(counter%RING_SIZE)-1]=0;
      counter++;
      pr_debug(" %d ",(counter%RING_SIZE)-1);
    }
    pr_debug("SingingFirewall: Resyncing %d<---%d!\n",tone_counter,tone_pos_counter);
    tone_pos_counter=tone_counter;
  } 
}

static int thread_code(void *data) {
  unsigned long timeout_1,timeout_2;
  u16 tone_;
  u8 Save;

  daemonize("KSingingFirewall");
  allow_signal(SIGTERM); 

  while(1) {
    if(tone[tone_counter]!=0) {
      tone_ = CLOCK_TICK_RATE/tone[tone_counter];
      timeout_1=HZ/50; 
      outb(0xb6, 0x43);
      outb_p(tone_ & 0xff, 0x42);
      outb((tone_>>8) & 0xff, 0x42);
      Save = inb(0x61);
      outb(Save|0x03, 0x61);
      timeout_1=wait_event_interruptible_timeout(
        wq, (timeout_1==0), timeout_1);
      if(timeout_1==-ERESTARTSYS) break;
      outb(inb_p(0x61) & 0xFC, 0x61);
      tone[tone_counter]=0;
      update_tone_counter();
      pr_debug("Tone Counter: %d\n",tone_counter);
    }  
    timeout_2=HZ/100;
    timeout_2=wait_event_interruptible_timeout(
      wq, (timeout_2==0), timeout_2);
    if(timeout_2==-ERESTARTSYS) break;   
  }
  thread_id = 0;
  complete_and_exit(&on_exit, 0);
}
    
unsigned int hook_func(
  unsigned int hooknum,
  struct sk_buff **skb,
  const struct net_device *in,
  const struct net_device *out,
  int (*okfn)(struct sk_buff *))
{
  struct tcphdr *thead; 
  struct sk_buff *sk=*skb;
  u16 port;
  char check;

  check=0;
  if (sk->nh.iph->protocol == IPPROTO_TCP)
    {tone[tone_pos_counter]=100; check=1;}
  if (sk->nh.iph->protocol == IPPROTO_UDP)
    {tone[tone_pos_counter]=200; check=1;}
  if (sk->nh.iph->protocol == IPPROTO_ICMP)
    {tone[tone_pos_counter]=300; check=1;}
  if (!check) return NF_ACCEPT;

  update_tone_pos_counter();
  check_syncing();
  
  pr_debug("Tone Pos Counter: %d\n",tone_pos_counter);

  check=0;
  if (sk->nh.iph->protocol == IPPROTO_TCP) {
    thead=(struct tcphdr *) (sk->data + (sk->nh.iph->ihl * 4));
    port=ntohs(thead->dest);
    switch( port )
    {
      case 21 : {tone[tone_pos_counter]=1000;check=1;break;}
      case 22 : {tone[tone_pos_counter]=1500;check=1;break;}
      case 80 : {tone[tone_pos_counter]=2000;check=1;break;} 
      case 443: {tone[tone_pos_counter]=2500;check=1;break;} 
    }	

    if (check) {
      update_tone_pos_counter(); 
      check_syncing(); 
      pr_debug("Tone Pos Counter: %d\n",tone_pos_counter);
    }
  }
  return NF_ACCEPT;          
}

int init_module() {
  int counter;
  for (counter=0; counter<=RING_SIZE-1; counter++)
    tone[counter]=0;
  tone_counter=0;
  tone_pos_counter=0;
    
  init_waitqueue_head(&wq);
  thread_id=kernel_thread(thread_code, NULL, CLONE_KERNEL);
  if(thread_id==0) return -EIO;  

  nfho_out.hook     = hook_func;        
  nfho_out.hooknum  = NF_IP_LOCAL_OUT;
  nfho_out.pf       = PF_INET;
  nfho_out.priority = NF_IP_PRI_FIRST;  

  nfho_in.hook      = hook_func;        
  nfho_in.hooknum   = NF_IP_LOCAL_IN;
  nfho_in.pf        = PF_INET;
  nfho_in.priority  = NF_IP_PRI_FIRST;      

  nf_register_hook(&nfho_out);
  nf_register_hook(&nfho_in);    
  return 0;
}
	
void cleanup_module() {
    if(thread_id) kill_proc(thread_id, SIGTERM, 1);
    wait_for_completion(&on_exit);
    nf_unregister_hook(&nfho_in);
    nf_unregister_hook(&nfho_out);    
    outb(inb_p(0x61) & 0xFC, 0x61);
}
