/* Internet Control Message Protocol */
#include "global.h"
#include "mbuf.h"
#include "internet.h"
#include "timer.h"
#include "iface.h"
#include "ip.h"
#include "icmp.h"

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 mbuf *htonicmp();
	struct icmp icmp;	/* ICMP header */
	struct ip ip;		/* Offending datagram header */
	int16 type;		/* Type of ICMP message */
	extern int pingflag;	/* DG2KK */

	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;
	}
	ntohicmp(&icmp,&bp);

	/* Process the message. Some messages are passed up to the protocol
	 * module for handling, others are handled here.
	 */
	type = icmp.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 */
		ntohip(&ip,&bp);	/* Extract offending IP header */
		switch(ip.protocol){
		case TCP_PTCL:
			tcp_icmp(ip.source,ip.dest,icmp.type,icmp.code,&bp);
			break;
		}
		break;
	case ECHO:		/* Echo Request */
		/* Change type to ECHO_REPLY, recompute checksum,
		 * and return datagram.
		 */
		/* DG2KK: don't send an ECHO_REPLY if pingflag is 'off' */
		if(pingflag == 0)
			return;
		icmp.type = ECHO_REPLY;
		bp = htonicmp(&icmp,bp);
		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 */
		echo_proc(source,dest,&icmp);
		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(ip,bp,type,code,args)
struct ip *ip;		/* Header of offending datagram */
struct mbuf *bp;	/* Data portion of datagram */
char type,code;		/* Codes to send */
union icmp_args *args;
{
	struct mbuf *htonicmp();
	struct mbuf *htonip();
	struct icmp icmp;	/* ICMP protocol header */
	int16 dlen;		/* Length of data portion of offending pkt */
	int16 length;		/* Total length of reply */
	extern int32 ip_addr;	/* Our IP address */

	if(ip == NULLIP)
		return;
	if(type < ICMP_TYPES)
		icmp_stats.output[type]++;

	if(ip->protocol == ICMP_PTCL){
		/* Never send an ICMP message about another ICMP message */
		icmp_errors.noloop++;
		return;
	}
	/* Compute amount of original datagram to return.
	 * We return the original IP header, and up to 8 bytes past that.
	 */
	dlen = min(8,len_mbuf(bp));
	length = dlen + ICMPLEN + IPLEN + ip->optlen;
	if(bp != NULLBUF){
		/* Take excerpt from data portion */
		bp = copy_p(bp,dlen);
	}
	/* Recreate and tack on offending IP header */
	bp = htonip(ip,bp);

	icmp.type = type;
	icmp.code = code;
	switch(icmp.type){
	case PARAM_PROB:
		icmp.args.pointer = args->pointer;
		break;
	case REDIRECT:
		icmp.args.address = args->address;
		break;
	case ECHO:
	case ECHO_REPLY:
	case INFO_RQST:
	case INFO_REPLY:
	case TIMESTAMP:
	case TIME_REPLY:
		icmp.args.echo.id = args->echo.id;
		icmp.args.echo.seq = args->echo.seq;
		break;
	default:
		icmp.args.unused = 0;
		break;
	}
	bp = htonicmp(&icmp,bp);	/* Now stick on the ICMP header */

	ip_send(ip_addr,ip->source,ICMP_PTCL,ip->tos,0,bp,length,0,0);
}
/* Generate ICMP header in network byte order, link data, compute checksum */
struct mbuf *
htonicmp(icmp,data)
struct icmp *icmp;
struct mbuf *data;
{
	struct mbuf *rval;
	register char *cp;
	int16 checksum;

	rval = alloc_mbuf(ICMPLEN);
	rval->cnt = ICMPLEN;
	cp = rval->data;

	*cp++ = icmp->type;
	*cp++ = icmp->code;
	cp = put16(cp,0);		/* Clear checksum */
	cp = put16(cp,icmp->args.echo.id);
	cp = put16(cp,icmp->args.echo.seq);

	/* Link in data, compute checksum, and stash result */
	rval->next = data;
	checksum = cksum(NULLHEADER,rval,len_mbuf(rval));
	cp = &rval->data[2];
	cp = put16(cp,checksum);

	return rval;
}
/* Pull off ICMP header */
ntohicmp(icmp,bpp)
struct icmp *icmp;
struct mbuf **bpp;
{
	icmp->type = pullchar(bpp);
	icmp->code = pullchar(bpp);
	(void) pull16(bpp);		/* Toss checksum */
	icmp->args.echo.id = pull16(bpp);
	icmp->args.echo.seq = pull16(bpp);
}
