/* Copyright 1984 by the Massachusetts Institute of Technology */

/* This file contains routines to handle IN packet input checks and
 * forwarding. This is all done in one place to cut down on stack
 * frame construction overhead, since this is all the processing that
 * most packets will need.
 */

/*
 *------------------------------------------------------------------
 *
 * $Source: /afs/net.mit.edu/project/cgw/src/gw/in/RCS/infwd.c,v $
 * $Revision: 1.7 $
 * $Date: 90/09/29 20:09:28 $
 * $State: Exp $
 * $Author: jon $
 * $Locker:  $
 *
 * $Log:	infwd.c,v $
 * Revision 1.7  90/09/29  20:09:28  jon
 * don't send redirects if ip_send_redirects is not set
 * 
 * Revision 1.6  89/03/15  08:37:34  jon
 * add support for PPP proxy interfaces which forward packets addressed to
 * them down the PPP line instead of responding ourselves
 * 
 * Revision 1.5  89/03/05  22:57:53  jon
 * add call to send ttl exceeded icmp messages
 * 
 * Revision 1.4  88/10/30  20:25:50  jon
 * Add support for outgoing fragmentation.
 * 
 * Revision 1.3  87/06/24  14:11:49  jon
 * Hack better RVD flushing and get rid of cryptic abbreviations in messages.
 * 
 * 
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_statd_c = "$Header: /afs/net.mit.edu/project/cgw/src/gw/in/RCS/infwd.c,v 1.7 90/09/29 20:09:28 jon Exp $";
#endif	lint

#include	<types.h>
#include	<sys.h>
#include	"../src/const.h"
#include	"../src/param.h"
#include	"../src/defs.h"
#include	"../src/macs.h"
#include	"../src/net.h"
#include	"../src/ext.h"
#include	"in.h"
#include	"inparam.h"
#include	"inext.h"
#include	"inrte.h"
#include        "inpro.h"

#include        "rvd.h"

/* #include	"../ch/ch.h" */



/* Macros; make code smaller and more readable.
 */

#define	infrme()	{ if (gethq(&inlpq) == NULL) \
				addtsk(syshnd, INPPRI, inxmux, 0); \
			addq(&inlpq, iob); \
/*			if (*inmsgq == NULL) \
				signal(inpid, inopc, 0); */ \
			return; }

#define	inlog(rsn)	dolog(rsn, mksina(&pkt->i_src), mksina(&pkt->i_dst));

ext char *rvd_packet_names[];

#define log_rvd_flush(rvdtype) dolog (rvdflush, rvd_packet_names[rvdtype-1], mksina (&pkt->i_src), mksina (&pkt->i_dst));

/* Error messages */

static char inqo[] = "IN queue overflow packet from %d.%d.%d.%d to %d.%d.%d.%d on net %d interface %s/%d\n";
static char innvf[] = "IN not V4 packet headr %d on net %d interfac %s/%d\n";
static char inht[] = "IN header trunc %d packet len %d on net %d interfac %s/%d\n";
static char inbc[] = "IN bad header checksum packet from %d.%d.%d.%d to %d.%d.%d.%d net %d interface %s/%d\n";
static char inttlz[] = "IN TTL ran out packet from %d.%d.%d.%d to %d.%d.%d.%d\n";
static char inpt[] = "IN packet truncated %d packet len %d packet from %d.%d.%d.%d to %d.%d.%d.%d\n";
/* now also used in infrg.c */
char indsc[] = "IN packet %d from %d.%d.%d.%d to %d.%d.%d.%d disc, rsn %d\n";
static char rvdflush[] = "IN flushed RVD %s packet from %d.%d.%d.%d to %d.%d.%d.%d.\n";

/* Add buffer to IP packet queue; discard if queue overflows.
 */

inaddq(iob)
reg	iorb	*iob;

{	reg	net	*netp;
	reg	inpkt	*pkt;
		void	inxfwd();

	if ((numq(&inq) >= bufshr) && (nbfree <= buflow)) {
		netp = mknet(iob->i_usr2);
		pkt = mkin(iob->i_addr);
		niflog(L_INFC)
			dolog(inqo, mksina(&pkt->i_src), mksina(&pkt->i_dst),
				mksnet(netp));
		inovfin[netp->n_net]++;
		freebuf(iob);
		return;
		}
	if (gethq(&inq) == NULL)
		addtsk(syshnd, INPPRI, inxfwd, 0);
	addq(&inq, iob);
}


/* IN packet handler; loops over waiting packet list while nothing
 * more important is happening.  The strange name is so that things
 * get loaded from the library right.
 */

inxfwd()
{
    while (tstneq(&inq))
      infwd(rmq(&inq));
}


/* Handle an incoming IN packet. Checksum the header, swab the
 * length field, check against the iorb length, decrement the
 * TTL, etc. Forward on, either directly or to a gateway.
 * Send redirect if next hop gw anet/subnet == src host anet/subnet
 * and host is w/in mit subnet, etc.
 */

infwd(iob)
iorb	*iob;
{
    reg	inpkt	*pkt;
    reg	net	*netp;
    reg	unsw	tmp;
	unsw	fofflg;
	inaddr	hst;
	inia	*inina;
	word	code;
	void	inxmux();
	iprte	*ipr;
    ext	net	*inhop();
    ext int     ip_send_redirects;
    
    pkt = mkin(iob->i_addr);
    
    if ((pkt->i_ihlver & INHVERM) != INHVER) {
	niflog(L_ERRU)
	  dolog(innvf, (pkt->i_ihlver & INHVERM),
		mksnet(mknet(iob->i_usr2)), iob->i_usr6);
	freebuf(iob);
	return;
    }
    
    tmp = (pkt->i_ihlver & INHLENM);
    if (tmp < INMINH) {
	niflog(L_ERRU)
	  dolog(inht, tmp, iob->i_bxfr,
		mksnet(mknet(iob->i_usr2)));
	freebuf(iob);
	return;
    }
    tmp *= INWPHW;
    
    if ((cksum(pkt, tmp, 0) + 1) != 0) {
	niflog(L_ERRU)
	  dolog(inbc, mksina(&pkt->i_src), mksina(&pkt->i_dst),
		mksnet(mknet(iob->i_usr2)));
	freebuf(iob);
	return;
    }
    if (--pkt->i_ttl == 0) {
	niflog(L_ERRU)
	  inlog(inttlz);
	netp = mknet(iob->i_usr2);
	icmp_ttl_exceeded((iniatlst[netp->n_net]->inia_addr), pkt, ICMPTTTLE);
	freebuf(iob);
	return;
    }
    pkt->i_cksum = acksum(INTTLCKA, pkt->i_cksum);
    
    if (swab(pkt->i_len) > iob->i_bxfr) {
	niflog(L_ERRU)
	  dolog(inpt, swab(pkt->i_len), iob->i_bxfr,
		mksina(&pkt->i_src), mksina(&pkt->i_dst));
	freebuf(iob);
	return;
    }
    fofflg = swab(pkt->i_fofflg);
    if ((fofflg & INUNUF) != 0) {
	niflog(L_ERRU)
	  dolog(indsc, pkt->i_prot, mksina(&pkt->i_src),
		mksina(&pkt->i_dst), 0);
	freebuf(iob);
	return;
    }
    
    /* RVD punting ... */
    if (in_flush_rvd && pkt->i_prot == INRVD) {
      struct rvdhdr *rvdpkt;
      byte rvd_packet_type;
      unsl rvd_packet_type_mask;

      rvdpkt = ((struct rvdhdr *) (((char * ) pkt) +
			 ((pkt->i_ihlver & INHLENM) * INBPHW)));
      rvd_packet_type = rvdpkt->rvd_type;
      rvd_packet_type_mask = 1 << (rvd_packet_type - 1);
      if (in_flush_rvd == FLUSH_RVD_ALL) {
	log_rvd_flush (rvd_packet_type);
	freebuf (iob);
	return;
	}
      switch (rvd_packet_type) {
         case RVDSPIN:
	   if ((in_flush_rvd & rvd_packet_type_mask) &&
	       (in_flush_rvd_spin_mode & rvdpkt->mode)) {
	     log_rvd_flush (rvd_packet_type);
	     freebuf (iob);
	     return;
	   }
	   break;
         case RVDAUTHSPIN:
	   if ((in_flush_rvd & rvd_packet_type_mask) &&
	       (in_flush_rvd_spin_mode & rvdpkt->mode)) {
	     log_rvd_flush (rvd_packet_type);
	     freebuf (iob);
	     return;
	   }
	   break;
	 default:
	   if (in_flush_rvd & rvd_packet_type_mask) {
	     log_rvd_flush (rvd_packet_type);
	     freebuf (iob);
	     return;
	   }
	 }
    }

    niflog(L_TRCP)
      inlog("IN %d.%d.%d.%d -> %d.%d.%d.%d\n");
    
    /* This should be replaced soon with the real internet broadcast
     * standard.  For now, assume that any packet which was broadcast
     * on the local net is for me. */
    if ((pkt->i_dst.i_saddr.i_shst == INLNBRD) || (iob->i_stat & I_BRD))
      infrme();

    /* Route packet */
    if ((netp = inhop(iob, &hst)) == NULL)
      return;
    
    /* See if packet is addressed to me. */
    inina = iniatlst[netp->n_net];
    while (inina != NULL) {
        /* It's my packet if the dst is the same as one of my addresses
	   on the outgoing interface.  Unless we're fronting for a host
	   on the far end of a point to point link */
	if ((pkt->i_dst.i_long == mkina(inina->inia_addr)->i_long)
	    && !(inina->inia_flags & IF_PPP_PROXY))
	  infrme();
	inina = inina->inia_link;
    }
    
    /* Decide whether to send ICMP redirect */

    if (ip_send_redirects && netp == mknet(iob->i_usr2) &&
	pkt->i_prot != INICMP &&
	( ((swab(pkt->i_fofflg)) & INFOFFM) == 0) ) {
	    ipr = ipr_lookup(pkt->i_src.i_long);
	    if (ipr != NULL && ipr->ipr_type == T_DIR && ipr->ipr_netp == netp)
	      inredir((iniatlst[netp->n_net]->inia_addr), &hst, pkt);
	}
    
    if ((code = (*netp->n_send)(iob, netp, P_IN, &hst)) != D_OK) {
	niflog(L_TRCO)
	  dolog(indsc, pkt->i_prot, mksina(&pkt->i_src),
		mksina(&pkt->i_dst), code);
	freebuf(iob);
	return;
    }
}

/* Add buffer to IP packet transmit queue */
inxaddq(iob)
iorb	*iob;
{
	void	inxxmt();

	if (gethq(&inxq) == NULL)
		addtsk(syshnd, INPPRI, inxxmt, 0);
	addq(&inxq, iob);
}

/* IN xmt packet handler */
inxxmt()
{
	while(tstneq(&inxq)) {
/*		if (*gwmsgq != NULL) {
			addtsk(gwhnd, INPPRI, &inxxmt, 0);
			return;
			}	This mechanism should be reimplemented */
		inxmt(rmq(&inxq));
		}
}

inxmt(iob)
iorb	*iob;
{
	reg	net	*netp;
		inaddr	hst;
	ext	net	*inhop();
		word	code;
		inpkt	*pkt;

	if ((netp = inhop(iob, &hst)) == NULL)
		return;

	if ((code = (*netp->n_send)(iob, netp, P_IN, &hst)) != D_OK) {
		pkt = mkin(iob->i_addr);
		niflog(L_TRCO)
			dolog(indsc, pkt->i_prot, mksina(&pkt->i_src),
				mksina(&pkt->i_dst), code);
		freebuf(iob);
		return;
		}
}

