/*
* mod_icmp module kernel
* by: Fryxar
* e-mail: fryxar@yahoo.com.ar
*
* This module acts like an icmp proxy for echo/echo-reply packets at kernel level, 
* preventing icmp tunnels through firewalls or directly connections to a server.
*
* WARNNING:
* It doesn't support fragmented packet (at time...)
*
* Why do not use an userland icmp proxy?
*   1. You must disable icmp kernel handler to use it, so you need to patch the kernel anyway.
*   2. You don't need to modify ipchains and iptables politics
*   
* TODO:
*    Support fragmented packets
*    Support another icmp messages
*    Replace TRANSFORM algorithm
*    Any other suggest?
*
* COMPILE & RUN:
* gcc -DMODULE -D__KERNEL__ -O -Wall -c mod_icmp.c -I/usr/src/linux/include
* insmod mod_icmp.o
*
*/

#include <linux/config.h>
#include <linux/version.h>
#include <linux/module.h>

#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <linux/netdevice.h>
#include <linux/kernel.h>
#include <linux/if_ether.h>
#include <linux/time.h>

#define IP_MF 0x2000
#define IP_OFFMASK 0x1fff 
//#define TRANSFORM(x) (~x)           // Trivial transform
#define TRANSFORM(x,b) (x^b)          // Yet another trivial transform :-)

struct packet_type icmp_proto;
unsigned char seed;

unsigned short icmp_cksum(unsigned short *ptr,int nbytes)
{
register long sum;
unsigned short oddbyte;
register unsigned short anwser;
 
   sum = 0;
   while(nbytes>1)
   {
     sum += *ptr++;
     nbytes -= 2;
   }
   if(nbytes==1)
   {
     oddbyte = 0;
     *((unsigned char *) & oddbyte) = *(unsigned char *)ptr;
     sum += oddbyte;
   }
   sum = (sum >> 16) + (sum & 0xffff);
   sum += (sum >> 16);
   anwser = ~sum;

return(anwser);
}

/* Packet Handler Function */
int icmp_func(struct sk_buff *skb, struct device *dev, struct packet_type *pt) {
int i;

   if(htons(skb->mac.ethernet->h_proto) != ETH_P_IP) goto bye;

   skb->h.raw = skb->nh.raw + skb->nh.iph->ihl*4;

// Fragmented packet not supported
   if(htons(skb->nh.iph->frag_off)&(IP_OFFMASK|IP_MF)) goto bye;

   if(skb->nh.iph->protocol != IPPROTO_ICMP) goto bye;

   if(skb->h.icmph->type != ICMP_ECHO &&
      skb->h.icmph->type != ICMP_ECHOREPLY) goto bye;

// Process icmp packet
   skb->len = htons(skb->nh.iph->tot_len) -
     sizeof(struct iphdr) - sizeof(struct icmphdr);

   skb->data = (skb->h.raw) + sizeof(struct icmphdr);

   for(i = 0; i < skb->len; i++) 
     skb->data[i] = TRANSFORM(skb->data[i], seed);
                  
// Calculate checksums again
   skb->h.icmph->checksum = 0;
   skb->h.icmph->checksum = icmp_cksum(skb->h.raw, skb->len
                             + sizeof(struct icmphdr));

bye:
   kfree_skb(skb);

return 0;
}

int init_module(void) {
   
   seed =            (unsigned char)get_cmos_time();
   icmp_proto.dev =  NULL;
   icmp_proto.type = htons(ETH_P_ALL); 
   icmp_proto.func = icmp_func;

   dev_add_pack(&icmp_proto);

return(0);
}

void cleanup_module(void) {
   dev_remove_pack(&icmp_proto);
}
