/*
 *  icmp.c,v 1.26 1993/12/02 03:22:34 jch Exp
 */

/* Gated Release 3.5 */
/* Copyright (c) 1990,1991,1992,1993,1994 by Cornell University. All rights */
/* reserved. Refer to Particulars and other Copyright notices at the end of this */
/* file. */


/*
 *  Routines for handling ICMP messages
 */

#include "include.h"
#include "inet.h"
#include "icmp.h"
#include "ip_icmp.h"

#if	defined(PROTO_ICMP)

#ifdef	KRT_RT_IOCTL
#define	ICMP_PROCESS_REDIRECTS
#endif

#ifdef	PROTO_RDISC
#define	ICMP_PROCESS_RDISC
#endif

#if	defined(ICMP_PROCESS_REDIRECTS) || defined(ICMP_PROCESS_RDISC)
#define	ICMP_PROCESS
#endif

task *icmp_task = (task *) 0;
trace *icmp_trace_options = (trace *) 0;

static block_t icmp_block_index;

struct icmptype {
    const char *typename;
    u_int codes;
    const char *codenames[6];
};

static const struct icmptype icmp_types[ICMP_MAXTYPE] =
{
    {"EchoReply", 0,
     {""}},
    {"", 0,
     {""}},
    {"", 0,
     {""}},
    {"UnReachable", 5,
   {"Network", "Host", "Protocol", "Port", "NeedFrag", "SourceFailure"}},
    {"SourceQuench", 0,
     {""}},
    {"ReDirect", 4,
     {"Network", "Host", "TOSNetwork", "TOSHost"}},
    {"", 0,
     {""}},
    {"", 0,
     {""}},
    {"Echo", 0,
     {""}},
    {"RouterAdvertisement", 0,
     {""}},
    {"", 0,
     {"RouterSolicitation"}},
    {"TimeExceeded", 1,
     {"InTransit", "Reassembly"}},
    {"ParamProblem", 0,
     {""}},
    {"TimeStamp", 0,
     {""}},
    {"TimeStampReply", 0,
     {""}},
    {"InfoRequest", 0,
     {""}},
    {"InfoRequestReply", 0,
     {""}},
    {"MaskRequest", 0,
     {""}},
    {"MaskReply", 0,
     {""}}
};

static const flag_t icmp_trace_masks[ICMP_MAXTYPE + 1] = {
    TR_ICMP_DETAIL_INFO,	/* 0 - Echo Reply */
    TR_ALL,			/* 1 - Invalid */
    TR_ALL,			/* 2 - Invalid */
    TR_ICMP_DETAIL_ERROR,	/* 3 - Unreachable */
    TR_ICMP_DETAIL_ERROR,	/* 4 - Source Quench */
    TR_ICMP_DETAIL_REDIRECT,	/* 5 - Redirect */
    TR_ALL,			/* 6 - Invalid */
    TR_ALL,			/* 7 - Invalid */
    TR_ICMP_DETAIL_INFO,	/* 8 - Echo Request */
    TR_ICMP_DETAIL_ROUTER,	/* 9 - Router Advertisement */
    TR_ICMP_DETAIL_ROUTER,	/* 10 - Router Solicitation */
    TR_ICMP_DETAIL_ERROR,	/* 11 - Time exceeded */
    TR_ICMP_DETAIL_ERROR,	/* 12 - Parameter Problem */
    TR_ICMP_DETAIL_INFO,	/* 13 - Timestamp Request */
    TR_ICMP_DETAIL_INFO,	/* 14 - Timestamp Reply */
    TR_ICMP_DETAIL_INFO,	/* 15 - Info Request */
    TR_ICMP_DETAIL_INFO,	/* 16 - Info Reply */
    TR_ICMP_DETAIL_INFO,	/* 17 - Mask Request */
    TR_ICMP_DETAIL_INFO		/* 18 - Mask Reply */
};

const bits icmp_trace_types[] = {
    { TR_DETAIL,		"detail packets" },
    { TR_DETAIL_SEND,	"detail send packets" },
    { TR_DETAIL_RECV,	"detail recv packets" },
    { TR_PACKET,		"packets" },
    { TR_PACKET_SEND,	"send packets" },
    { TR_PACKET_RECV,	"recv packets" },
    { TR_DETAIL_1,	"detail redirect" },
    { TR_DETAIL_SEND_1,	"detail send redirect" },
    { TR_DETAIL_RECV_1,	"detail recv redirect" },
    { TR_PACKET_1,	"redirect" },
    { TR_PACKET_SEND_1,	"send redirect" },
    { TR_PACKET_RECV_1,	"recv redirect" },
    { TR_DETAIL_2,	"detail router" },
    { TR_DETAIL_SEND_2,	"detail send router" },
    { TR_DETAIL_RECV_2,	"detail recv router" },
    { TR_PACKET_2,	"router" },
    { TR_PACKET_SEND_2,	"send router" },
    { TR_PACKET_RECV_2,	"recv router" },
    { TR_DETAIL_3,	"detail info" },
    { TR_DETAIL_SEND_3,	"detail send info" },
    { TR_DETAIL_RECV_3,	"detail recv info" },
    { TR_PACKET_3,	"info" },
    { TR_PACKET_SEND_3,	"send info" },
    { TR_PACKET_RECV_3,	"recv info" },
    { TR_DETAIL_4,	"detail error" },
    { TR_DETAIL_SEND_4,	"detail send error" },
    { TR_DETAIL_RECV_4,	"detail recv error" },
    { TR_PACKET_4,	"error" },
    { TR_PACKET_SEND_4,	"send error" },
    { TR_PACKET_RECV_4,	"recv error" },
    { 0, NULL }
};

struct icmp_list {
    struct icmp_list *forw, *back;
    sockaddr_un *who;
    struct icmp pkt;
};


/*
 * Trace received ICMP messages
 */
static void
#ifdef	ICMP_NOIP_HEADER
icmp_trace __PF4(from, sockaddr_un *,
		 icmp, struct icmp *,
		 msg, const char *,
		 detail, int)
#else	/* ICMP_NOIP_HEADER */
icmp_trace __PF5(from, sockaddr_un *,
		 to, sockaddr_un *,
		 icmp, struct icmp *,
		 msg, const char *,
		 detail, int)
#endif	/* ICMP_NOIP_HEADER */
{
    const char *type_name, *code_name;
    const struct icmptype *itp;

    if (icmp->icmp_type < ICMP_MAXTYPE) {
	itp = &icmp_types[icmp->icmp_type];
	type_name = itp->typename;
	if (icmp->icmp_code <= itp->codes) {
	    code_name = itp->codenames[icmp->icmp_code];
	} else {
	    code_name = "Invalid";
	}
    } else {
	type_name = "Invalid";
	code_name = "";
    }

#ifdef	ICMP_NOIP_HEADER
    tracef("ICMP RECV %A type %s(%u) code %s(%u)",
	   from,
	   type_name,
	   icmp->icmp_type,
	   code_name,
	   icmp->icmp_code);
#else	/* ICMP_NOIP_HEADER */
    tracef("ICMP RECV %A -> %A type %s(%u) code %s(%u)",
	   from,
	   to,
	   type_name,
	   icmp->icmp_type,
	   code_name,
	   icmp->icmp_code);
#endif	/* ICMP_NOIP_HEADER */

    if (msg) {
	tracef(": %s",
	       msg);
    }
    
    if (detail) {
	int do_mask = FALSE;
	int do_dest = FALSE;
	int do_idseq = FALSE;
    
	switch (icmp->icmp_type) {
	case ICMP_ECHOREPLY:
	case ICMP_ECHO:
	    do_idseq = TRUE;
	    break;
    
	case ICMP_UNREACH:
	    /* XXX - Port? */
	    do_dest = TRUE;
	    break;
	    
	case ICMP_SOURCEQUENCH:
	    break;
    
	case ICMP_REDIRECT:
	    tracef(" dest %A via %A",
		   sockbuild_in(0, icmp->icmp_ip.ip_dst.s_addr),
		   sockbuild_in(0, icmp->icmp_gwaddr.s_addr));
	    break;
    
	case ICMP_TIMXCEED:
	case ICMP_PARAMPROB:
	    do_dest = TRUE;
	    break;

	case ICMP_TSTAMP:
	case ICMP_TSTAMPREPLY:
	    tracef(" originate %T.%u receive %T.%u transmit %T.%u",
		   icmp->icmp_otime / 1000,
		   icmp->icmp_otime % 1000,
		   icmp->icmp_rtime / 1000,
		   icmp->icmp_rtime % 1000,
		   icmp->icmp_ttime / 1000,
		   icmp->icmp_ttime % 1000);
	    break;
    
	case ICMP_IREQ:
	case ICMP_IREQREPLY:
	    do_idseq = do_dest = TRUE;
	    break;
    
	case ICMP_MASKREQ:
	case ICMP_MASKREPLY:
	    do_idseq = do_mask = TRUE;
	    break;

	case ICMP_ROUTERADV:
	    {
		int i = icmp->icmp_addrnum;
		
		tracef(" addresses %d entries per %d lifetime %#T",
		       icmp->icmp_addrnum,
		       icmp->icmp_addrsiz,
		       ntohs(icmp->icmp_lifetime));
		    
		while (i--) {
		    trace_only_tf(icmp_trace_options,
				  0,
				  (NULL));
		    tracef("ICMP RECV	router %A preference %u",
			   sockbuild_in(0, icmp->icmp_rdisc[i].ird_addr.s_addr),
			   ntohl(icmp->icmp_rdisc[i].ird_pref));
		}			
	    }
	    break;

	case ICMP_ROUTERSOL:
	    break;
	}

	if (do_idseq) {
	    tracef(" id %u sequence %u",
		   icmp->icmp_id,
		   icmp->icmp_seq);
	}
	if (do_dest) {
	    tracef(" dest %A protocol %s(%u)",
		   sockbuild_in(0, icmp->icmp_ip.ip_dst.s_addr),
		   trace_value(inet_proto_bits, icmp->icmp_ip.ip_p),
		   icmp->icmp_ip.ip_p);
	}
	if (do_mask) {
	    tracef(" mask %A",
		   sockbuild_in(0, icmp->icmp_mask));
	}
    }
    trace_only_tf(icmp_trace_options,
		  0,
		  (NULL));
}


/*
 * icmp_recv() handles ICMP redirect messages.
 */

static void
icmp_recv __PF1(tp, task *)
{
    int n_packets = TASK_PACKET_LIMIT * 10;
    size_t count;
    register struct icmp_list *il;
    static struct icmp_list head = { &head, &head };

    while (n_packets-- && !task_receive_packet(tp, &count)) {
#ifndef	ICMP_NOIP_HEADER
	struct ip *ip;
#endif	/* ICMP_NOIP_HEADER */
	struct icmp *icmp;
	const char *msg = "";
#ifdef	ICMP_PROCESS
	int freeit = 0;
#endif	/* ICMP_PROCESS */

	/* Allocate a block to store this packet */
	il = (struct icmp_list *) task_block_alloc(icmp_block_index);
#ifndef	ICMP_NOIP_HEADER
	task_parse_ip(ip, icmp, struct icmp *);

	count -= sizeof (struct ip);
#endif	/* ICMP_NOIP_HEADER */
	il->who = sockdup(task_recv_srcaddr);

	/* Save the packet */
        bcopy((caddr_t) icmp,
	      (caddr_t) &il->pkt,
	      MIN(count, sizeof (struct icmp)));

#ifdef	ICMP_PROCESS
	switch (il->pkt.icmp_type) {
	    register struct icmp_list *ilp;

#ifdef	ICMP_PROCESS_REDIRECTS
	case ICMP_REDIRECT:
	    /* Check for duplicate */
	    for (ilp = head.forw; ilp != &head; ilp = ilp->forw) {
		if ((sock2ip(il->who) == sock2ip(ilp->who)) &&
		    (il->pkt.icmp_type == ilp->pkt.icmp_type) &&
		    (il->pkt.icmp_code == ilp->pkt.icmp_code) &&
		    (il->pkt.icmp_gwaddr.s_addr == ilp->pkt.icmp_gwaddr.s_addr) &&
		    (il->pkt.icmp_ip.ip_dst.s_addr == ilp->pkt.icmp_ip.ip_dst.s_addr)) {
		    /* Found a duplicate */

		    msg = "Duplicate";
		    goto free_up;
		}
	    }
	    /* Fall through */
#endif	/* ICMP_PROCESS_REDIRECTS */

#ifdef	ICMP_PROCESS_RDISC
	case ICMP_ROUTERADV:
	case ICMP_ROUTERSOL:
#endif	/* ICMP_PROCESS_RDISC */

	    /* Not a duplicate, add to end of list */
	    INSQUE(il, head.back);
	    break;

	default:
	free_up:
	    freeit++;
	}
#endif	/* ICMP_PROCESS */

	if (TRACE_PACKET_RECV_TP(tp,
				 il->pkt.icmp_type,
				 ICMP_MAXTYPE,
				 icmp_trace_masks)) {
	    icmp_trace(il->who,
#ifndef	ICMP_NOIP_HEADER
		       sockbuild_in(0, ip->ip_dst.s_addr),
#endif	/* ICMP_NOIP_HEADER */
		       &il->pkt,
		       msg,
		       TRACE_DETAIL_RECV_TP(tp,
					    il->pkt.icmp_type,
					    ICMP_MAXTYPE,
					    icmp_trace_masks));
	}

#ifdef	ICMP_PROCESS
	if (freeit) {
#endif	/* ICMP_PROCESS */
	    sockfree(il->who);
	    task_block_free(icmp_block_index, (void_t) il);
#ifdef	ICMP_PROCESS
	}
#endif	/* ICMP_PROCESS */
    }

#ifdef	ICMP_PROCESS
    while ((il = head.forw) != &head) {
	/* Process the requests */

	switch (il->pkt.icmp_type) {
	    /* Types to keep are filtered above */
#ifdef	ICMP_PROCESS_REDIRECTS
	case ICMP_REDIRECT:
	    {
		sockaddr_un *gateway, *dest, *mask;
    
		gateway = sockdup(sockbuild_in(0, il->pkt.icmp_gwaddr.s_addr));
		dest = sockdup(sockbuild_in(0, il->pkt.icmp_ip.ip_dst.s_addr));

		switch (il->pkt.icmp_code) {
		case ICMP_REDIRECT_NET:
		case ICMP_REDIRECT_TOSNET:
		    mask = inet_mask_natural(dest);
		    goto do_redirect;

		case ICMP_REDIRECT_HOST:
		case ICMP_REDIRECT_TOSHOST:
		    mask = inet_mask_host;

		do_redirect:
		    redirect(dest, mask, gateway, il->who);
		}

		sockfree(dest);
		sockfree(gateway);
	    }
	    break;
#endif	/* ICMP_PROCESS_REDIRECTS */

#ifdef	ICMP_PROCESS_RDISC
	case ICMP_ROUTERADV:
	    rdisc_recv_adv(il->who, il->pkt);
	    break;

	case ICMP_ROUTERSOL:
	    rdisc_recv_sol(il->who, il->pkt);
	    break;
#endif	/* ICMP_PROCESS_RDISC */

	}

	/* Free this entry */
	sockfree(il->who);
	REMQUE(il);
	task_block_free(icmp_block_index, (void_t) il);
    }
#endif	/* ICMP_PROCESS */

    return;
}


/*
 *  Initialize ICMP socket and ICMP task
 */

void
icmp_var_init __PF0(void)
{
}


static void
icmp_cleanup __PF1(tp, task *)
{
    trace_freeup(icmp_trace_options);
    trace_freeup(tp->task_trace);
}


static void
icmp_reinit __PF1(tp, task *)
{
    trace_inherit_global(icmp_trace_options, icmp_trace_types, (flag_t) 0);
    trace_set(tp->task_trace, icmp_trace_options);
}


static void
icmp_terminate __PF1(tp, task *)
{
    icmp_cleanup(tp);

    task_delete(tp);
}


void
icmp_init __PF0(void)
{

    if (!icmp_task) {
	trace_inherit_global(icmp_trace_options, icmp_trace_types, (flag_t) 0);
	icmp_task = task_alloc("ICMP",
			       TASKPRI_ICMP,
			       icmp_trace_options);
	icmp_task->task_proto = IPPROTO_ICMP;
	task_set_recv(icmp_task, icmp_recv);
	task_set_cleanup(icmp_task, icmp_cleanup);
	task_set_reinit(icmp_task, icmp_reinit);
	task_set_terminate(icmp_task, icmp_terminate);
	if ((icmp_task->task_socket = task_get_socket(icmp_task, AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
	    task_quit(errno);
	}
	if (!task_create(icmp_task)) {
	    task_quit(EINVAL);
	}

        if (task_set_option(icmp_task,
			    TASKOPTION_NONBLOCKING,
			    TRUE) < 0) {
	    task_quit(errno);
        }
	task_alloc_recv(icmp_task, ICMP_PACKET_MAX);

	/* Do all we can to avoid losing packets */
	if (task_set_option(icmp_task,
			    TASKOPTION_RECVBUF,
			    task_maxpacket) < 0) {
	    task_quit(errno);
	}

	icmp_block_index = task_block_init(sizeof (struct icmp_list), "icmp_list");
    }
}

#endif	/* defined(PROTO_ICMP) && !defined(KRT_RT_SOCK) */


/*
 * ------------------------------------------------------------------------
 * 
 * 	GateD, Release 3.5
 * 
 * 	Copyright (c) 1990,1991,1992,1993,1994 by Cornell University
 * 	    All rights reserved.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY
 * 	EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
 * 	LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * 	AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * 	Royalty-free licenses to redistribute GateD Release
 * 	3 in whole or in part may be obtained by writing to:
 * 
 * 	    GateDaemon Project
 * 	    Information Technologies/Network Resources
 * 	    200 CCC
 * 	    Cornell University
 * 	    Ithaca, NY  14853-2601  USA
 * 
 * 	GateD is based on Kirton's EGP, UC Berkeley's routing
 * 	daemon	 (routed), and DCN's HELLO routing Protocol.
 * 	Development of GateD has been supported in part by the
 * 	National Science Foundation.
 * 
 * 	Please forward bug fixes, enhancements and questions to the
 * 	gated mailing list: gated-people@gated.cornell.edu.
 * 
 * ------------------------------------------------------------------------
 * 
 *       Portions of this software may fall under the following
 *       copyrights:
 * 
 * 	Copyright (c) 1988 Regents of the University of California.
 * 	All rights reserved.
 * 
 * 	Redistribution and use in source and binary forms are
 * 	permitted provided that the above copyright notice and
 * 	this paragraph are duplicated in all such forms and that
 * 	any documentation, advertising materials, and other
 * 	materials related to such distribution and use
 * 	acknowledge that the software was developed by the
 * 	University of California, Berkeley.  The name of the
 * 	University may not be used to endorse or promote
 * 	products derived from this software without specific
 * 	prior written permission.  THIS SOFTWARE IS PROVIDED
 * 	``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
 * 	INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * 	MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
