/* hello.c 
 * Copyright (C) 1998 by Ori Pomerantz
 * 
 * "Hello, world" - the kernel module version. 
 */

/* The necessary header files */

/* Standard in kernel modules */
#include <linux/config.h>
#include <linux/skbuff.h>
#include <linux/kmod.h>
#include <linux/vmalloc.h>
#include <linux/netdevice.h>
#include <linux/module.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <net/ip.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
#include <asm/checksum.h>
#include <linux/proc_fs.h>


#include <linux/kernel.h>   /* We're doing kernel work */
#include <linux/module.h>   /* Specifically, a module */

#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_nat_lock)
#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_nat_lock)

#include <linux/netfilter_ipv4/ip_nat.h>
#include <linux/netfilter_ipv4/ip_nat_rule.h>
#include <linux/netfilter_ipv4/ip_nat_protocol.h>
#include <linux/netfilter_ipv4/ip_nat_core.h>
#include <linux/netfilter_ipv4/ip_nat_helper.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ip_conntrack_core.h>
#include <linux/netfilter_ipv4/listhelp.h>

#include <linux/netfilter.h>

/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif        


u_int16_t
ip_nat_cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
{
    u_int32_t diffs[] = { oldvalinv, newval };
    return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
                      oldcheck^0xFFFF));
}

static unsigned int addr192_168_1_17 = 0x1101a8c0;
static unsigned int addr192_168_1_81 = 0x5101a8c0; 
static unsigned int addr10_10_10_1   = 0x010a0a0a;
static unsigned int addr10_10_10_3   = 0x030a0a0a;

static unsigned int
ip_nat_fn(unsigned int hooknum,
      struct sk_buff **pskb,
      const struct net_device *in,
      const struct net_device *out,
      int (*okfn)(struct sk_buff *))
{
	/* 
	 * The pre-routing function
	 */

	unsigned int saddr;
	unsigned int daddr;
	struct iphdr *iph;
	struct udphdr *udphdr;

	iph = (*pskb)->nh.iph;
	udphdr = (struct udphdr*)((u_int32_t *)iph + iph->ihl);
	
    if (iph->protocol == 17)
    {
        /*
         * It is UDP
         */

		saddr = iph->saddr;
		daddr = iph->daddr;
		if (saddr == addr192_168_1_17 && 
			daddr == addr192_168_1_81)
		{
			/* Found the match. Change daddr to 10.10.10.3 */
			daddr = addr10_10_10_3;		/* 10.10.10.3 */
			iph->check = ip_nat_cheat_check(~iph->daddr, daddr, iph->check);
        	iph->daddr = daddr;

			(*pskb)->nfcache |= NFC_UNKNOWN;	/* not sure whether should do this */
			(*pskb)->nfcache |= NFC_ALTERED;
			(*pskb)->nfcache |= NFC_IP_DST;
		}
    }

	return NF_ACCEPT;
}


static unsigned int
ip_nat_out(unsigned int hooknum,
       struct sk_buff **pskb,
       const struct net_device *in,
       const struct net_device *out,
       int (*okfn)(struct sk_buff *))
{
	/*
	 * The post-routing function
	 */
    unsigned int saddr;
    unsigned int daddr;

    struct iphdr *iph;
    struct udphdr *udphdr;

    iph = (*pskb)->nh.iph;
    udphdr = (struct udphdr*)((u_int32_t *)iph + iph->ihl);

    if (iph->protocol == 17)
    {
        /*
         * It is UDP
         */
        saddr = iph->saddr;
        daddr = iph->daddr;

        if (saddr == addr192_168_1_17 && daddr == addr10_10_10_3)
        {
			/* Change the source address to 10.10.10.1 */	
            saddr = addr10_10_10_1;      
            iph->check = ip_nat_cheat_check(~iph->saddr, saddr, iph->check);
            iph->saddr = saddr;

			(*pskb)->nfcache |= NFC_UNKNOWN;	/* again, not sure whether should do it */
			(*pskb)->nfcache |= NFC_ALTERED;
            (*pskb)->nfcache |= NFC_IP_SRC;
        }
    }

    return NF_ACCEPT;
}


static unsigned int
ip_nat_local_fn(unsigned int hooknum,
        struct sk_buff **pskb,
        const struct net_device *in,
        const struct net_device *out,
        int (*okfn)(struct sk_buff *))
{
	return NF_ACCEPT;
}

// = { { NULL, NULL }, ip_nat_fn, PF_INET, NF_IP_PRE_ROUTING, NF_IP_PRI_NAT_DST };

/* Before packet filtering, change destination */
static struct nf_hook_ops ip_nat_in_ops
= { { NULL, NULL }, ip_nat_fn, PF_INET, NF_IP_PRE_ROUTING, NF_IP_PRI_NAT_DST-1};
/* After packet filtering, change source */
static struct nf_hook_ops ip_nat_out_ops
= { { NULL, NULL }, ip_nat_out, PF_INET, NF_IP_POST_ROUTING, NF_IP_PRI_NAT_SRC-1};
/* Before packet filtering, change destination */
static struct nf_hook_ops ip_nat_local_out_ops
= { { NULL, NULL }, ip_nat_local_fn, PF_INET, NF_IP_LOCAL_OUT, NF_IP_PRI_NAT_DST-1 };


/* Initialize the module */
int init_module()
{

  /* If we return a non zero value, it means that 
   * init_module failed and the kernel module 
   * can't be loaded */

    int ret = 0;

  	printk("Hello, world - this is the little netfilter module\n");

    ret = nf_register_hook(&ip_nat_in_ops);
    if (ret < 0) 
	{
        printk("ip_nat_init: can't register in hook.\n");

		// MUST_BE_READ_WRITE_UNLOCKED(&ip_nat_lock);
		return ret;
    }

    ret = nf_register_hook(&ip_nat_out_ops);
    if (ret < 0) 
	{
        printk("ip_nat_init: can't register out hook.\n");
		return ret;
    }

    ret = nf_register_hook(&ip_nat_local_out_ops);
    if (ret < 0) 
	{
        printk("ip_nat_init: can't register local out hook.\n");
		return ret;
    }

    return ret;


  return 0;
}


/* Cleanup - undid whatever init_module did */
void cleanup_module()
{
  	printk("Short is the life of a kernel module\n");

	nf_unregister_hook(&ip_nat_in_ops);
	nf_unregister_hook(&ip_nat_out_ops);
	nf_unregister_hook(&ip_nat_local_out_ops);
}

