/*
 *	The beginnings of an ICMP implementation.
 *	Most message types not implemented, for now only
 *	DEST UNREACHABLE, TIME EXCEEDED, ECHO REPLY work.
 *
 *	02/05/94, Kay Roemer.
 */

#include "config.h"
#include "kerbind.h"
#include "sockerr.h"
#include "atarierr.h"
#include "icmp.h"
#include "buf.h"
#include "inet.h"
#include "ip.h"
#include "if.h"
#include "route.h"

static long	icmp_input (struct netif *, BUF *, unsigned long,
			unsigned long);
static long	icmp_error (short, short, BUF *, unsigned long,
			unsigned long);

struct in_ip_ops icmp_ops = {
	IPPROTO_ICMP,
	0,
	icmp_error,
	icmp_input
};

static BUF	*do_send (short, short, BUF *, BUF *);
static short	do_unreach (BUF *);
static void	icmp_erreport (BUF *);

static long
icmp_input (nif, buf, saddr, daddr)
	struct netif *nif;
	BUF *buf;
	unsigned long saddr, daddr;
{
	struct icmp_dgram *icmph;
	short datalen;

	icmph = (struct icmp_dgram *)IP_DATA (buf);

	datalen = (long)buf->dend - (long)icmph;
	if (datalen & 1) *buf->dend = 0;
	if (chksum (icmph, (datalen+1)/2)) {
		DEBUG (("icmp_input: bad checksum from 0x%lx", saddr));
		buf_deref (buf, BUF_NORMAL);
		return 0;
	}

	switch (icmph->type) {
	case ICMPT_ECHORP:
		TRACE (("icmp_input: ECHO REPLY from 0x%lx", saddr));
		return -1;

	case ICMPT_DSTUR:
		TRACE (("icmp_input: DESTINATION UNREACH. from 0x%lx", saddr));
		do_unreach (buf);
		icmp_erreport (buf);
		return -1;

	case ICMPT_TIMEX:
		TRACE (("icmp_input: TIME EXCEEDED from 0x%lx", saddr));
		icmp_erreport (buf);
		return -1;

	case ICMPT_SRCQ:
		TRACE (("icmp_input: SOURCE QUENCH from 0x%lx", saddr));
		icmp_erreport (buf);
		return -1;

	case ICMPT_REDIR:
		TRACE (("icmp_input: REDIRECT from 0x%lx", saddr));
		return -1;

	case ICMPT_ECHORQ:
		TRACE (("icmp_input: ECHO REQUEST from 0x%lx", saddr));
		icmp_send (ICMPT_ECHORP, 0, saddr, buf, 0);
		return 0;

	case ICMPT_PARAMP:
		TRACE (("icmp_input: PARAMETER PROBLEM from 0x%lx", saddr));
		icmp_erreport (buf);
		return -1;

	case ICMPT_TIMERQ:
		TRACE (("icmp_input: TIME REQUEST from 0x%lx", saddr));
		buf_deref (buf, BUF_NORMAL);
		return 0;

	case ICMPT_TIMERP:
		TRACE (("icmp_input: TIME REPLY from 0x%lx", saddr));
		buf_deref (buf, BUF_NORMAL);
		return 0;

	case ICMPT_INFORQ:
		TRACE (("icmp_input: INFO REQUEST from 0x%lx", saddr));
		buf_deref (buf, BUF_NORMAL);
		return 0;

	case ICMPT_INFORP:
		TRACE (("icmp_input: INFO REPLY from 0x%lx", saddr));
		return -1;

	case ICMPT_MASKRQ:
		TRACE (("icmp_input: MASK REQUEST from 0x%lx", saddr));
		buf_deref (buf, BUF_NORMAL);
		return 0;

	case ICMPT_MASKRP:
		TRACE (("icmp_input: MASK REPLY from 0x%lx", saddr));
		return -1;
	}
	return -1;
}

static long
icmp_error (type, code, buf, saddr, daddr)
	short type, code;
	BUF *buf;
	unsigned long saddr, daddr;
{
	TRACE (("icmp_error: cannot send icmp error message to %lx", daddr));
	buf_deref (buf, BUF_NORMAL);
	return 0;
}

long
icmp_errno (type, code)
	short type, code;
{
	static short icmp_errors[] = {
		ENETUNREACH,	/* net unreachable */
		EHOSTUNREACH,	/* host unreachable */
		ENOPROTOOPT,	/* protocol unreachable */
		ECONNREFUSED,	/* port unreachable */
		EOPNOTSUPP,	/* frag needed but DF set */
		EOPNOTSUPP,	/* source soute failed */
	};

	if (type != ICMPT_DSTUR)
		return EOPNOTSUPP;

	if ((unsigned short)code > sizeof (icmp_errors)/sizeof (*icmp_errors)) {
		DEBUG (("icmp_errno: %d: no such code", code));
		return EINTRN;
	}
	return icmp_errors[code];
}

short
icmp_dontsend (type, buf)
	short type;
	BUF *buf;
{
	struct ip_dgram *iph = (struct ip_dgram *)buf->dstart;

	if (iph->proto == IPPROTO_ICMP) switch (type) {
	case ICMPT_DSTUR:
	case ICMPT_REDIR:
	case ICMPT_SRCQ:
	case ICMPT_TIMEX:
	case ICMPT_PARAMP:
		return 1;
	}
	if (iph->fragoff & IP_FRAGOFF)
		return 1;

	if (ip_is_brdcst_addr (iph->daddr))
		return 1;

	return 0;
}

/* the parameters:
 *	buf1 holds the original IP datagram that caused the ICMP request.
 *	buf2 holds optional data specific for ICMP type/code, 0 if none.
 */

long
icmp_send (type, code, daddr, buf1, buf2)
	short type, code;
	unsigned long daddr;
	BUF *buf1, *buf2;
{
	BUF *nbuf;
	struct icmp_dgram *icmph;
	unsigned long saddr;
	short datalen;

	if (icmp_dontsend (type, buf1)) {
		DEBUG (("icmp_send: error message about error message"));
		buf_deref (buf1, BUF_NORMAL);
		if (buf2) buf_deref (buf2, BUF_NORMAL);
		return 0;
	}
	saddr = IP_DADDR (buf1);
	if (!ip_is_local_addr (saddr))
		saddr = INADDR_ANY;

	nbuf = do_send (type, code, buf1, buf2);
	if (!nbuf) {
		buf_deref (buf1, BUF_NORMAL);
		if (buf2) buf_deref (buf2, BUF_NORMAL);
		return 0;
	}

	icmph = (struct icmp_dgram *)nbuf->dstart;
	icmph->code = code;
	icmph->type = type;
	icmph->chksum = 0;

	datalen = (long)nbuf->dend - (long)nbuf->dstart;
	if (datalen & 1) *nbuf->dend = 0;
	icmph->chksum = chksum (icmph, (datalen+1)/2);

	ip_send (saddr, daddr, nbuf, IPPROTO_ICMP, 0, 0);
	if (buf2) buf_deref (buf2, BUF_NORMAL);
	return 0;
}

static BUF *
do_send (type, code, buf1, buf2)
	short type, code;
	BUF *buf1, *buf2;
{
	BUF *nbuf;
	struct icmp_dgram *icmph;

	switch (type) {
	case ICMPT_ECHORP:
		buf1->dstart = IP_DATA (buf1);
		return buf1;
		
	case ICMPT_DSTUR:
	case ICMPT_TIMEX:
		/* Keep only the first 64 bits of the original datagram */
		buf1->dend = IP_DATA (buf1) + 2 * sizeof (long);

		nbuf = buf_reserve (buf1, sizeof (*icmph), BUF_RESERVE_START);
		if (!nbuf) {
			DEBUG (("do_send: no space for ICMP header"));
			return 0;
		}
		nbuf->dstart -= sizeof (*icmph);
		icmph = (struct icmp_dgram *)nbuf->dstart;
		icmph->u.zero = 0;
		return nbuf;

	case ICMPT_SRCQ:
	case ICMPT_REDIR:
	case ICMPT_ECHORQ:
	case ICMPT_PARAMP:
	case ICMPT_TIMERQ:
	case ICMPT_TIMERP:
	case ICMPT_INFORQ:
	case ICMPT_INFORP:
	case ICMPT_MASKRQ:
	case ICMPT_MASKRP:
		break;
	}
	TRACE (("do_send: ICMP message type %d not implemented", type));
	return 0;
}

static short
do_unreach (buf)
	BUF *buf;
{
	struct icmp_dgram *icmph;
	struct ip_dgram *iph;

	icmph = (struct icmp_dgram *)IP_DATA (buf);
	iph = (struct ip_dgram *)(icmph+1);

	if (icmph->code == ICMPC_HOSTUR || icmph->code == ICMPC_NETUR) {
		struct route *rt = route_get (iph->daddr);
		if (rt) {
			DEBUG (("do_unreach: bad route to %lx", rt->net));
			rt->flags |= RTF_REJECT;
			route_deref (rt);
		}
	}
	return 0;
}

static void
icmp_erreport (b)
	BUF *b;
{
	extern struct in_ip_ops *allipprotos;
	struct in_ip_ops *p;
	struct icmp_dgram *icmph;
	struct ip_dgram *iph;
	BUF *buf;

	buf = buf_clone (b, BUF_NORMAL);
	if (buf == 0)
		return;

/* strip IP header */
	buf->dstart = IP_DATA (buf);
	icmph = (struct icmp_dgram *)buf->dstart;

/* strip ICMP header */
	buf->dstart += sizeof (struct icmp_dgram);
	iph = (struct ip_dgram *)buf->dstart;

	for (p = allipprotos; p; p = p->next) {
		if (p->proto == iph->proto) {
			(*p->error) (icmph->type, icmph->code, buf,
				iph->saddr, iph->daddr);
			return;
		}
	}
	DEBUG (("icmp_erreport: %d: no such proto", iph->proto));
	buf_deref (buf, BUF_NORMAL);
}

void
icmp_init (void)
{
	ip_register (&icmp_ops);
}
