/*
 *	NET3:	Implementation of the ICMP protocol layer. 
 *	
 *		Alan Cox, <A.Cox@swansea.ac.uk / gw4pts@gw4pts.ampr.org>
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 *
 *	Some of the function names and the icmp unreach table for this
 *	module were derived from [icmp.c 1.0.11 06/02/93] by
 *	Ross Biro, Fred N. van Kempen, Mark Evans, Alan Cox, Gerhard Koerting.
 *	Other than that this module is a complete rewrite.
 *
 *	Fixes:
 *		Ulrich Kunitz	:	Fixed ICMP timestamp reply.
 */
 
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/fcntl.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/string.h>
#include "snmp.h"
#include "ip.h"
#include "route.h"
#include "protocol.h"
#include "icmp.h"
#include "tcp.h"
#include "snmp.h"
#include <linux/skbuff.h>
#include <linux/netprotocol.h>
#include "sock.h"
#include <linux/errno.h>
#include <linux/timer.h>
/*#include <asm/system.h>
 #include <asm/segment.h>*/


#define min(a,b)	((a)<(b)?(a):(b))


struct protocol proto_icmp;



/*
 *	Statistics
 */
 
struct icmp_mib	icmp_statistics={0,};


/* An array of errno for error messages from dest unreach. */
/* RFC 1122: 3.2.2.1 States that NET_UNREACH, HOS_UNREACH and SR_FAIELD MUST be considered 'transient errrs'. */

struct icmp_err icmp_err_convert[] = {
  { ENETUNREACH,	0 },	/*	ICMP_NET_UNREACH	*/
  { EHOSTUNREACH,	0 },	/*	ICMP_HOST_UNREACH	*/
  { ENOPROTOOPT,	1 },	/*	ICMP_PROT_UNREACH	*/
  { ECONNREFUSED,	1 },	/*	ICMP_PORT_UNREACH	*/
  { EOPNOTSUPP,		0 },	/*	ICMP_FRAG_NEEDED	*/
  { EOPNOTSUPP,		0 },	/*	ICMP_SR_FAILED		*/
  { ENETUNREACH,	1 },	/* 	ICMP_NET_UNKNOWN	*/
  { EHOSTDOWN,		1 },	/*	ICMP_HOST_UNKNOWN	*/
  { ENONET,		1 },	/*	ICMP_HOST_ISOLATED	*/
  { ENETUNREACH,	1 },	/*	ICMP_NET_ANO		*/
  { EHOSTUNREACH,	1 },	/*	ICMP_HOST_ANO		*/
  { EOPNOTSUPP,		0 },	/*	ICMP_NET_UNR_TOS	*/
  { EOPNOTSUPP,		0 }	/*	ICMP_HOST_UNR_TOS	*/
};

static unsigned long dummy;	/* Uses less memory and is faster to have a 4 byte scribble area
				   than all the if x==NULL crap */

struct icmp_control
{
	unsigned long *output;		/* Address to increment on output */
	unsigned long *input;		/* Address to increment on input */
	void (*handler)(struct icmphdr *icmph, sk_buff *skb, struct device *dev, unsigned long saddr, unsigned long daddr, int len);
};

static struct icmp_control icmp_pointers[19];


/*
 *	Maintain the counters in the SNMP statistics for outgoing ICMP
 */
 
static void icmp_out_count(int type)
{
	if(type>18)
		return;
	(*icmp_pointers[type].output)++;
	icmp_statistics.IcmpOutMsgs++;
}


static sk_buff *icmp_alloc_skb(int length, int priority)
{
	sk_buff *skb=alloc_skb(length+protocol_size(&proto_icmp), priority);
	if(skb==NULL)
	{
		icmp_statistics.IcmpOutErrors++;
		return NULL;
	}
	protocol_adjust(skb, &proto_icmp);
	skb->free=1;
	return skb;
}

/*
 *	Take an ICMP sk_buff (including header). Checksum it and send 
 *	it onwards.
 */

static void icmp_send_skb(sk_buff *skb, long saddr,long daddr, int tos, unsigned short type, unsigned short code, unsigned long gateway)
{
	struct icmphdr *icmph;
	struct ip_opt opt;
	
	opt.ttl=255;
	opt.tos=tos;
	opt.opt=NULL;
	opt.family=AF_INET;

	/*
	 *	Fill in the frame
	 */
	 
	icmph = (struct icmphdr *)skb_push(skb,sizeof(struct icmphdr));
	icmph->type = type;
	icmph->code = code;
	icmph->un.gateway = gateway;
	icmph->checksum = 0;	
	icmph->checksum = ip_compute_csum((unsigned char *)icmph,skb->len);
	icmp_out_count(icmph->type);

	if(proto_ip.output(&proto_ip,skb, ETH_P_IP, IPPROTO_ICMP, &saddr, &daddr, &opt))
		icmp_statistics.IcmpOutErrors++;
}

/*
 *	Send an ICMP message in response to a situation
 *
 *	RFC 1122: 3.2.2	MUST send at least the IP header and 8 bytes of header. MAY send more (we don't).
 *			MUST NOT change this header information.
 *			MUST NOT reply to a multicast/broadcast IP address.
 *			MUST NOT reply to a multicast/broadcast MAC address.
 *			MUST reply to only the first fragment.
 */
 
void icmp_send(sk_buff *skb_in, struct iphdr *iph, int type, int code, struct device *dev)
{
	sk_buff *skb;
	int len;
	unsigned char *data;
	int addrtype;
	unsigned long saddr;
	
	if(iph==NULL)
	{
		printk("icmp_send: NULL iph!\n");
		return;
	}
	
	/*
	 *	ICMP's to broadcast or multicasts are right OUT
	 *
	 *	RFC 1122: 3.2.2  MUST NOT send an ICMP error to a broadcast/multicast target.
	 *		  MUST NOT send an ICMP error to a broadcast/multicast MAC source.
	 *	          MUST NOT send an ICMP error to a non-initial fragment.
	 *	FIXME	  MUST NOT send to an broadcast/multicast source (gets fixed when IP is fixed)
	 */
	 
	addrtype=ip_chk_addr(iph->daddr);
	
	if(addrtype==IS_BROADCAST || addrtype==IS_MULTICAST 
		|| skb_in->pkt_type != PACKET_HOST
		|| (ntohs(iph->frag_off)&0x1FFF))
		return;	

	/*
	 *	We must NEVER NEVER send an ICMP error to an ICMP error message
	 *
	 *	RFC 1122: 3.2.2. MUST NOT send an ICMP error in reply to an ICMP error.
	 */
	 
	 
	if(type==ICMP_DEST_UNREACH||type==ICMP_REDIRECT||type==ICMP_SOURCE_QUENCH||type==ICMP_TIME_EXCEEDED)
	{
		if(iph->protocol==IPPROTO_ICMP)
		{
/*			printk("icmp_send: Reject ICMP\n");*/
			return;
		}
	}

	/*
	 *	Get some memory for the reply. 
	 */
	 
	len = sizeof(struct iphdr) + 8;	/* amount of header to return */
	   
	if((skb = (sk_buff *) icmp_alloc_skb(len, GFP_ATOMIC))==NULL)
	{
		printk("ICMP send: no memory\n");
		return;
	}
	
	/*
	 *	Fill in the data
	 */

	data=skb_put(skb,sizeof(struct iphdr)+8);
	if(data==NULL)
	{	
		printk("skb_put: icmp_send botch 1 [BANG!]\n");
		skb->sk=NULL;
		kfree_skb(skb,FREE_READ);
		return;
	}
	memcpy(data, iph, sizeof(struct iphdr) + 8);

/*	printk("icmp err sent\n");*/

	saddr=iph->daddr;
	if(saddr!=dev->pa_addr && ip_chk_addr(saddr)!=IS_MYADDR)
		saddr=dev->pa_addr;
		
	icmp_send_skb(skb, saddr, iph->saddr, iph->tos, type, code, 0);
}
	
	


/* 
 *	Handle ICMP_DEST_UNREACH, ICMP_TIME_EXCEED, and ICMP_QUENCH. 
 */
 
static void icmp_unreach(struct icmphdr *icmph, sk_buff *skb, struct device *dev, unsigned long saddr, unsigned long daddr, int len)
{
	struct iphdr *iph;

	skb->h.raw=(void *)icmph;	/* Raw socket view starts here */
		
	iph = (struct iphdr *) (icmph + 1);
	
	if(icmph->type==ICMP_DEST_UNREACH)
	{
		switch(icmph->code & 15)
		{
			case ICMP_NET_UNREACH:
				break;
			case ICMP_HOST_UNREACH:
				break;
			case ICMP_PROT_UNREACH:
				printk("ICMP: %s:%d: protocol unreachable.\n",
					in_ntoa(iph->daddr), ntohs(iph->protocol));
				break;
			case ICMP_PORT_UNREACH:
				break;
			case ICMP_FRAG_NEEDED:
				printk("ICMP: %s: fragmentation needed and DF set.\n",
								in_ntoa(iph->daddr));
				break;
			case ICMP_SR_FAILED:
				printk("ICMP: %s: Source Route Failed.\n", in_ntoa(iph->daddr));
				break;
			default:
				break;
		}
		if(icmph->code>12)	/* Invalid type */
			return;
	}
	
	/*
	 *	Throw it at our lower layers
	 *
	 *	RFC 1122: 3.2.2 MUST extract the protocol ID from the passed header.
	 *	RFC 1122: 3.2.2.1 MUST pass ICMP unreach messages to the transport layer.
	 *	RFC 1122: 3.2.2.2 MUST pass ICMP time expired messages to transport layer.
	 */

	if(protocol_pass_demultiplex(&proto_icmp, &iph->protocol, skb, &iph->saddr, &iph->daddr)==0)
	{
/*		printk("ICMP: no protocol\n");*/
		kfree_skb(skb, FREE_READ);
	}
}

/*
 *	Handle ICMP_REDIRECT. 
 */

static void icmp_redirect(struct icmphdr *icmph, sk_buff *skb, struct device *dev, unsigned long source, unsigned long daddr, int len)
{
	struct rtable *rt;
	struct iphdr *iph;
	unsigned long ip;

	/*
	 *	Get the copied header of the packet that caused the redirect
	 */
	 
	iph = (struct iphdr *) (icmph + 1);
	ip = iph->daddr;

	switch(icmph->code & 7) 
	{
		case ICMP_REDIR_NET:
			/*
			 *	This causes a problem with subnetted networks. What we should do
			 *	is use ICMP_ADDRESS to get the subnet mask of the problem route
			 *	and set both. But we don't.. (Neither does anyone else though!)
			 *
			 *	RFC 1122: 3.2.2.2
			 *	MUST honour NET rediect. The spec says we should adjust our routing
			 *	table as appropriate. We do on the basis the user knew best - ie don't change it
			 */
#ifdef not_a_good_idea
			ip_rt_add((RTF_DYNAMIC | RTF_MODIFIED | RTF_GATEWAY),
				ip, 0, icmph->un.gateway, dev,0, 0);
			break;
#endif
		case ICMP_REDIR_HOST:
			/*
			 *	Add better route to host.
			 *	But first check that the redirect
			 *	comes from the old gateway..
			 *
			 *	RFC 1122. 3.2.2.2 MUST honour HOST redirect. MUST check new gateway is valid
			 *
			 */
			rt = ip_rt_route(ip, NULL, NULL);
			if (!rt)
				break;
			if (rt->rt_gateway != source)
				break;
			printk("ICMP redirect from %s\n", in_ntoa(source));
			ip_rt_add((RTF_DYNAMIC | RTF_MODIFIED | RTF_HOST | RTF_GATEWAY),
				ip, 0, icmph->un.gateway, dev,0, 0);
			break;
		case ICMP_REDIR_NETTOS:
		case ICMP_REDIR_HOSTTOS:
			printk("ICMP: cannot handle TOS redirects yet!\n");
			break;
		default:
			break;
  	}
	kfree_skb(skb, FREE_READ);
}


/*
 *	Handle ICMP_ECHO ("ping") requests. 
 *
 *	RFC 1122: 3.2.2.6 MUST have an echo server that answers ICMP echo requests.
 *	RFC 1122: 3.2.2.6 Data received in the ICMP_ECHO request MUST be included in the reply.
 *	See also WRT handling of options once they are done and working.
 */
 
static void icmp_echo(struct icmphdr *icmph, sk_buff *skb, struct device *dev, unsigned long saddr, unsigned long daddr, int len)
{
	sk_buff *skb2;
	if ((skb2=icmp_alloc_skb(len, GFP_ATOMIC))!=NULL)
	{	
		memcpy(skb_put(skb2,len),(icmph+1),len);	/* Copy the data */
		/* Note: We want to copy the id and sequence. They overlap the gateway part of the union precisely.
		   This is portably safe being fundamentally a property of the ICMP structures */
		icmp_send_skb(skb2, daddr, saddr, skb->ip_hdr->tos, ICMP_ECHOREPLY, 0, icmph->un.gateway);
	}
	kfree_skb(skb, FREE_READ);
}

/*
 *	Handle ICMP Timestamp requests. 
 *	RFC 1122: 3.2.2.8 MAY implement ICMP timestamp requests.
 *		  SHOULD be in the kernel for minimum random latency.
 *		  MUST be accurate to a few minutes.
 *		  MUST be updated at least at 15Hz.
 */
 
static void icmp_timestamp(struct icmphdr *icmph, sk_buff *skb, struct device *dev,unsigned long saddr, unsigned long daddr, int len)
{
	sk_buff *skb2;
	unsigned long *timeptr, midtime;

	if(len!=20)
	{
		icmp_statistics.IcmpInErrors++;
		if(len<12)
			return;
	}
	if((skb2 = icmp_alloc_skb(20, GFP_ATOMIC))!=NULL)
	{
		/* fill in the current time as ms since midnight UT: */
		midtime = (xtime.tv_sec % 86400) * 1000 + xtime.tv_usec / 1000;
		timeptr = (unsigned long *) skb_put(skb2,20);
		memcpy(timeptr,(icmph+1),12);
		/*
		 *	the originate timestamp (timeptr [0]) is still in the copy: 
		 */
		timeptr [1] = timeptr [2] = htonl(midtime);
		icmp_send_skb(skb2, daddr, saddr, skb->ip_hdr->tos, ICMP_TIMESTAMPREPLY,0, icmph->un.gateway);
	}
	kfree_skb(skb, FREE_READ);
}
 
 
/* 
 *	Handle ICMP_ADDRESS_MASK requests.  (RFC950)
 *
 *	RFC 1122: 3.2.2.9	MUST NOT send address mask replies unless an authoritive agent.
 */
 
static void icmp_address(struct icmphdr *icmph, sk_buff *skb, struct device *dev, unsigned long saddr, unsigned long daddr, int len)
{
#if 0
	sk_buff *skb2;
	if((skb2 = icmp_alloc_skb(len, GFP_ATOMIC))!=NULL)
	{
		memcpy(skb_put(skb2,sizeof(dev->pa_mask)), (char *) &dev->pa_mask, sizeof(dev->pa_mask));
		icmp_send_skb(skb2, daddr,saddr,skb->ip_hdr->tos, ICMP_ADDRESSREPLY, 0, icmph->un.gateway);	
	}
#endif	
	kfree_skb(skb, FREE_READ);	
}

static void icmp_discard(struct icmphdr *icmph, sk_buff *skb, struct device *dev, unsigned long saddr, unsigned long daddr, int len)
{
	kfree_skb(skb, FREE_READ);
}


/* 
 *	Deal with incoming ICMP packets. 
 *
 *	Entry:
 *		skb->h.raw holds a pointer to the IP datagram header.
 *		The next pending data is the ICMP datagram header.
 *
 *	Calls:
 *		Internal handling routines for ICMP. User level access to ICMP
 *		is via SOCK_RAW (raw.c) not this module.
 *
 *	Return:
 *		The message has been dealt with and freed.
 */
 
int icmp_input(struct protocol *p, struct protocol *below, sk_buff *skb1, void *saddr_p, void *daddr_p)
{
	struct icmphdr *icmph;
	unsigned long saddr= *(long *)saddr_p;
	unsigned long daddr= *(long *)daddr_p;
	int len=skb1->len;

	icmp_statistics.IcmpInMsgs++;
  	
  	/*
  	 *	Grab the packet as an icmp object. IP has eaten the ip
  	 *	header already and passed it to us in bits.
  	 */

	icmph = (struct icmphdr *)skb_pull(skb1,sizeof(struct icmphdr),NULL);

	/*
	 *	Validate the packet first 
	 */

	if (ip_compute_csum((unsigned char *) icmph, len)) 
	{
		/* Failed checksum! */
		icmp_statistics.IcmpInErrors++;
		printk("ICMP: failed checksum from %s!\n", in_ntoa(saddr));
		kfree_skb(skb1, FREE_READ);
		return(0);
	}
	
	/*
	 *	18 is the highest 'known' icmp type. Anything else is a mystery
	 *
	 *	RFC 1122: 3.2.2  Unknown ICMP messages types MUST be silently discarded.
	 */
	 
	if(icmph->type > 18)
	{
		icmp_statistics.IcmpInErrors++;		/* Is this right - or do we ignore ? */
		kfree_skb(skb1,FREE_READ);
		return(0);
	}
	
	/*
	 *	Parse the ICMP message 
	 */

	if (ip_chk_addr(daddr) == IS_BROADCAST)
	{
		/*
		 *	RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be silently ignored (we don't as it is used
		 *	by some network mapping tools).
		 *	RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently discarded if to broadcast/multicast.
		 */
		if (icmph->type != ICMP_ECHO) 
		{
			icmp_statistics.IcmpInErrors++;
			kfree_skb(skb1, FREE_READ);
			return(0);
  		}
		daddr=skb1->dev->pa_addr;
	}
	
	len-=sizeof(struct icmphdr);

	(*icmp_pointers[icmph->type].input)++;
	(icmp_pointers[icmph->type].handler)(icmph,skb1,skb1->dev,saddr,daddr,len);
	return 0;
}

static struct icmp_control icmp_pointers[19]=
{
	&icmp_statistics.IcmpOutEchoReps, &icmp_statistics.IcmpInEchoReps, icmp_discard,		/* ECHO REPLY (0) */
	&dummy,&icmp_statistics.IcmpInErrors, icmp_discard,
	&dummy,&icmp_statistics.IcmpInErrors, icmp_discard,
	&icmp_statistics.IcmpOutDestUnreachs, &icmp_statistics.IcmpInDestUnreachs, icmp_unreach,	/* DEST UNREACH (3) */
	&icmp_statistics.IcmpOutSrcQuenchs, &icmp_statistics.IcmpInSrcQuenchs, icmp_unreach,		/* SOURCE QUENCH (4) */
	&icmp_statistics.IcmpOutRedirects,  &icmp_statistics.IcmpInRedirects, icmp_redirect,		/* REDIRECT (5) */
	&dummy,&icmp_statistics.IcmpInErrors, icmp_discard,
	&dummy,&icmp_statistics.IcmpInErrors, icmp_discard,
	&icmp_statistics.IcmpOutEchos,&icmp_statistics.IcmpInEchos, icmp_echo,				/* ECHO (8) */
	&dummy,&icmp_statistics.IcmpInErrors, icmp_discard,
	&dummy,&icmp_statistics.IcmpInErrors, icmp_discard,
	&icmp_statistics.IcmpOutTimeExcds, &icmp_statistics.IcmpInTimeExcds, icmp_unreach,		/* TIME EXCEEDED (11) */
	/* FIXME: RFC1122 3.2.2.5 - MUST pass PARAM_PROB messages to transport layer */
	&icmp_statistics.IcmpOutParmProbs, &icmp_statistics.IcmpInParmProbs, icmp_discard,		/* PARAMETER PROBLEM (12) */
	&icmp_statistics.IcmpOutTimestamps, &icmp_statistics.IcmpInTimestamps, icmp_timestamp,		/* TIMESTAMP (13) */
	&icmp_statistics.IcmpOutTimestampReps, &icmp_statistics.IcmpInTimestampReps, icmp_discard,	/* TIMESTAMP REPLY (14) */
	&dummy,&dummy, icmp_discard,									/* INFO (15) */
	&dummy,&dummy, icmp_discard,									/* INFO REPLY (16) */
	&icmp_statistics.IcmpOutAddrMasks, &icmp_statistics.IcmpInAddrMasks, icmp_address,		/* ADDR MASK (17) */
	&icmp_statistics.IcmpOutAddrMaskReps, &icmp_statistics.IcmpInAddrMaskReps, icmp_discard	/* ADDR MASK REPLY (18) */
};

void icmp_init(void)
{
	struct protocol *pr=protocol_find("IP");
	if(pr==NULL)
	{
		printk("ICMP: Cannot find IP in order to bind.\n");
		return;
	}
	protocol_register(&proto_icmp);
	protocol_bind(pr,&proto_icmp, ETH_P_IP, IPPROTO_ICMP);
}


static int icmp_get_key(int proto, int subid, unsigned char *key)
{
	if(proto!=ETH_P_IP)
		return -EAFNOSUPPORT;
	*key=subid;
	return 1;
}

/*
 *	Protocol descriptor for ICMP.
 */


struct protocol proto_icmp=
{
	NULL,
	"ICMP",
	sizeof(struct icmphdr),
	0,
	sizeof(struct icmphdr),
	0,
	NULL,
#ifdef CONFIG_FAST_PATH	
	NULL,
	NULL,
#endif	
	icmp_input,
	icmp_input,
	default_protocol_control,
	icmp_get_key,
	NULL,
	NULL
};
