/* Internet Control Message Protocol */

#include "machdep.h"
#include "internet.h"
#include "timer.h"
#include "ip.h"
#include "icmp.h"
#include "mbuf.h"

int (*echo_proc)();	/* Handler for Echo Reply messages */

struct icmp_errors icmp_errors;
struct icmp_stats icmp_stats;

/* Process an incoming ICMP packet */
void
icmp_input(bp,protocol,source,dest,tos,length,rxbroadcast)
struct mbuf *bp;	/* Pointer to ICMP message */
char protocol;		/* Should always be ICMP_PTCL */
int32 source;		/* Sender of ICMP message */
int32 dest;		/* Us */
char tos;		/* Type of Service */
int16 length;		/* Length of ICMP message */
char rxbroadcast;
{

	struct icmp *icmph;	/* Pointer to ICMP message */
	struct ip_header *iph;	/* Offending datagram header */
	int16 type;		/* Type of ICMP message */
	int16 ip_len;

	if(rxbroadcast){
		/* Broadcast ICMP packets are to be IGNORED !! */
		icmp_errors.bdcsts++;
		free_p(bp);
		return;
	}
	if(cksum(NULLHEADER,bp,length) != 0){
		/* Bad ICMP checksum; discard */
		icmp_errors.checksum++;
		free_p(bp);
		return;
	}
	/* If the message is fragmented, copy to a contiguous mbuf */
	if(bp->next != NULLBUF){
		struct mbuf *nbp;

		nbp = copy_p(bp,length);
		free_p(bp);
		if(nbp == NULLBUF){
			icmp_errors.nospace++;
			return;
		}
		bp = nbp;
	}
	icmph = (struct icmp *)bp->data;

	/* Process the message. Some messages are passed up to the protocol
	 * module for handling, others are handled here.
	 */
	type = icmph->type & 0xff;
	if(type < ICMP_TYPES)
		icmp_stats.input[type]++;

	switch(type){
	case TIME_EXCEED:	/* Time-to-live Exceeded */
	case DEST_UNREACH:	/* Destination Unreachable */
	case QUENCH:		/* Source Quench */
		iph = (struct ip_header *)(icmph + 1);
		ip_len = (iph->v_ihl & 0xf) * sizeof(int32);

		switch(iph->protocol){
		case TCP_PTCL:
			tcp_icmp(ntohl(iph->source),ntohl(iph->dest),
				icmph->type,icmph->code,(char *)iph + ip_len);
			break;
		}
		break;
	case ECHO:		/* Echo Request */
		/* Change type to ECHO_REPLY, recompute checksum,
		 * and return datagram.
		 */
		icmph->type = ECHO_REPLY;
		icmph->checksum = 0;
		icmph->checksum = cksum(NULLHEADER,bp,length);
		icmp_stats.output[ECHO_REPLY]++;
		ip_send(dest,source,ICMP_PTCL,tos,0,bp,length,0,0);
		return;
	case REDIRECT:		/* Redirect */
	case PARAM_PROB:	/* Parameter Problem */
		break;
       case ECHO_REPLY:		/* Echo Reply */
		if(echo_proc){
			(*echo_proc)(ntohl(iph->source),ntohl(iph->dest),
			 icmph->type,icmph->code,(char *)iph + ip_len);
		}
		break;
	case TIMESTAMP:		/* Timestamp */
	case TIME_REPLY:	/* Timestamp Reply */
	case INFO_RQST:		/* Information Request */
	case INFO_REPLY:	/* Information Reply */
		break;
	}
	free_p(bp);
}
/* Return an ICMP response to the sender of a datagram */
icmp_output(bp,type,code,args)
struct mbuf *bp;		/* Pointer to offending IP header + data */
char type,code;			/* Codes to send */
union icmp_args *args;
{
	struct ip_header *iph;	/* Offending IP header */
	int16 ip_len;		/* Length of offending IP header */

	struct mbuf *reply;	/* Buffer with ICMP reply */
	struct icmp *icmph;	/* ICMP protocol header */
	struct mbuf *data;	/* Returned portion of offending packet */
	int16 dlen;		/* Length of data portion of offending pkt */
	int16 length;		/* Total length of reply */
	extern int32 ip_addr;	/* Our IP address */

	if(type < ICMP_TYPES)
		icmp_stats.output[type]++;

	iph = (struct ip_header *)bp->data;

	if(iph->protocol == ICMP_PTCL){
		icmp_errors.noloop++;
		return;	/* Never send an ICMP message about another ICMP message */
	}
	/* Compute amount of original datagram to return.
	 * We return the original IP header, and up to 8 bytes past that.
	 */
	ip_len = (iph->v_ihl & 0xf) * sizeof(int32);
	dlen = ntohs(iph->length);
	if(dlen > ip_len + 8)
		dlen = ip_len + 8;
	length = sizeof(struct icmp) + dlen;

	/* Allocate ICMP header and fill in */
	if((reply = alloc_mbuf(sizeof(struct icmp))) == NULLBUF){
		/* No space; don't bother */
		icmp_errors.nospace++;
		return;
	}
	reply->cnt = sizeof(struct icmp);
	icmph = (struct icmp *)reply->data;
	icmph->type = type;
	icmph->code = code;
	if(args != (union icmp_args *)NULL)
		icmph->args.unused = args->unused;	/* copies whole union */
	else
		icmph->args.unused = 0;

	/* Link in a copy of the beginning of the original datagram */
	data = copy_p(bp,dlen);
	reply->next = data;	/* Could be NULL if copy fails */

	/* Compute ICMP checksum and send */
	icmph->checksum = 0;
	icmph->checksum = cksum(NULLHEADER,reply,length);

	ip_send(ip_addr,ntohl(iph->source),ICMP_PTCL,iph->tos,0,reply,length,0,0);
}
#ifdef	TRACE
/* ICMP message types */
char *icmptypes[] = {
	"Echo Reply",
	NULLCHAR,
	NULLCHAR,
	"Unreachable",
	"Source Quench",
	"Redirect",
	NULLCHAR,
	NULLCHAR,
	"Echo Request",
	NULLCHAR,
	NULLCHAR,
	"Time Exceeded",
	"Parameter Problem",
	"Timestamp",
	"Timestamp Reply",
	"Information Request",
	"Information Reply"
};

/* ICMP unreachable messages */
char *unreach[] = {
	"Network",
	"Host",
	"Protocol",
	"Port",
	"Fragmentation",
	"Source route"
};
/* ICMP Time exceeded messages */
char *exceed[] = {
	"Time-to-live",
	"Fragment reassembly"
};

/* ICMP redirect messages */
char *redirect[] = {
	"Network",
	"Host",
	"TOS & Network",
	"TOS & Host"
};

int
doicmpstat(argc,argv)
int argc;
char *argv[];
{
	extern struct icmp_errors icmp_errors;
	extern struct icmp_stats icmp_stats;
	register int i;

	printf("chksum err %u no space %u icmp %u bdcsts %u\r\n",
	 icmp_errors.checksum,icmp_errors.nospace,icmp_errors.noloop,
	 icmp_errors.bdcsts);
	printf("type  rcvd  sent\r\n");
	for(i=0;i<ICMP_TYPES;i++){
		if(icmp_stats.input[i] == 0 && icmp_stats.output[i] == 0)
			continue;
		printf("%-6u%-6u%-6u",i,icmp_stats.input[i],
			icmp_stats.output[i]);
		if(icmptypes[i] != NULLCHAR)
			printf("  %s",icmptypes[i]);
		printf("\r\n");
	}
	return 0;
}
/* Dump an ICMP header */
void
icmp_dump(bp,source,dest,check)
struct mbuf *bp;
int32 source,dest;
int check;		/* If 0, bypass checksum verify */
{
	register struct icmp *icmp;
	char *codemsg;
	struct mbuf *ibp;
	int i;
	char tmpbuf;

	if(bp == NULLBUF)
		return;
	/* If packet isn't in a single buffer, make a temporary copy and
	 * note the fact so we free it later
	 */
	if(bp->next != NULLBUF){
		bp = copy_p(bp,len_mbuf(bp));
		tmpbuf = 1;
	} else
		tmpbuf = 0;

	codemsg = NULLCHAR;
	icmp = (struct icmp *)bp->data;
	if(icmp->type <= 16 && icmptypes[icmp->type] != NULLCHAR)
		printf("ICMP: %s",icmptypes[icmp->type]);
	else
		printf("ICMP: type %u",icmp->type);

	switch(icmp->type){
	case DEST_UNREACH:
		if(icmp->code <= 5)
			codemsg = unreach[icmp->code];
		break;
	case REDIRECT:
		if(icmp->code <= 3)
			codemsg = redirect[icmp->code];
		break;
	case TIME_EXCEED:
		if(icmp->code <= 1)
			codemsg = exceed[icmp->code];
		break;
	}	
	if(codemsg != NULLCHAR)
		printf(" %s",codemsg);
	else
		printf(" code %u",icmp->code);

	/* Special case for parameter problem message */
	if(icmp->type == PARAM_PROB)
		printf(" pointer = 0x%x",icmp->args.pointer);

	if(check){
		/* Verify checksum */
		if((i = cksum(NULLHEADER,bp,len_mbuf(bp))) != 0)
			printf(" CHECKSUM ERROR (%u)",i);
	}
	printf("\r\n");
	/* Dump the offending IP header, if any */
	switch(icmp->type){
	case DEST_UNREACH:
	case TIME_EXCEED:
	case PARAM_PROB:
	case QUENCH:
	case REDIRECT:
		printf("Returned ");
		dup_p(&ibp,bp,sizeof(struct icmp),
			len_mbuf(bp) - sizeof(struct icmp));
		ip_dump(ibp);
		free_p(ibp);
	}
	if(tmpbuf)
		free_p(bp);
}
#endif
