BBN-Vax-TCP/bbnnet/icmp.c

#include "../h/param.h"
#include "../h/systm.h"
#include "../bbnnet/mbuf.h"
#include "../bbnnet/net.h"
#include "../bbnnet/ifcb.h"
#include "../bbnnet/tcp.h"
#include "../bbnnet/ip.h"
#include "../bbnnet/icmp.h"
#include "../bbnnet/ucb.h"

extern int nosum;

#define NICTYPE	17

/* ICMP message formats */
#define ICBAD	0	/* unimplemented */
#define ICERR	1	/* error format (use header) */
#define ICDAT	2	/* data format (use id) */
#define ICINT	3	/* data format (handle internally) */

char icaction[NICTYPE] = { ICDAT, ICBAD, ICBAD, ICERR, ICERR, ICERR, ICBAD,
			   ICBAD, ICINT, ICBAD, ICBAD, ICERR, ICERR, ICINT,
			   ICDAT, ICINT, ICDAT };

/*
 * Process ICMP messages.  Called directly from ip_input processor.
 */
icmp(mp, ifp)
register struct mbuf *mp;
struct ifcb *ifp;
{
	register struct ip *ip;
	register struct icmp *icp;
	register struct ucb *up;
	unsigned short i;
	int op, ilen;
	struct socket temp;
	struct ucb *icfind();

	ip = (struct ip *)((int)mp + mp->m_off);
	mp->m_off += sizeof(struct ip);
	mp->m_len -= sizeof(struct ip);
	icp = (struct icmp *)((int)ip + sizeof(struct ip));

	ilen = ip->ip_len;

	i = (unsigned short)icp->ic_sum;
	icp->ic_sum = 0;
	if (i != (unsigned short)cksum(mp, ilen)) {
		netstat.ic_badsum++;
		if (nosum)
			goto ignore;
		netlog(mp);
	} else {
ignore:
		/* decode message format, and associate msg with connection */

		if (icp->ic_type < NICTYPE)
			op = icaction[icp->ic_type];
		else
			op = ICBAD;

		switch (op) {

		case ICERR:	/* error format -- get conn from orig hdr */
			
			up = icfind(&icp->ic_data.hdr);
			break;

		case ICDAT:	/* data format -- get conn from id */

			/* Assume we sent the orig packet with id: id
			   is index in connection table (plus one).  Verify 
			   by bounds checking. */

			up = NULL;
			if ((i = icp->ic_misc.ic_iseq.id) != 0) {
				up = contab + (i-1);
				if (up >= conNCON || up->uc_proc == NULL)
					up = NULL;
			}
			break;

		case ICINT:	/* internal format -- don't need conn */

			break;

		default:	/* drop bad messages */
			netstat.ic_drops++;
			goto badret;
		}

		/* Now do any processing.  Internal messages get handled
		   here.  Others may be counted, or cause some other action */

		switch (icp->ic_type) {

		case 3:		/* destination unreachable */
			
			if (up != NULL)
				to_user(up, UNRCH);
			break;

		case 4:		/* source quench */

			netstat.ic_quenches++;
			break;
		
		case 5:		/* redirect */

			if (up != NULL) {

				/* free the old route and insert the new */

				if (up->uc_route != NULL)
					h_free(up->uc_route);
				up->uc_route = h_make(&icp->ic_misc.ic_gaddr);
				netstat.ic_redirects++;
			}
			break;

		case 8:		/* echo */

			/* reply to echo: swap src and dest addrs, change
			   type to zero, recompute checksum */

			icp->ic_type = 0;
			temp = ip->ip_src;
			ip->ip_src = ip->ip_dst;
			ip->ip_dst = temp;
			icp->ic_sum = cksum(mp, ilen);
			mp->m_off -= sizeof(struct ip);
			mp->m_len += sizeof(struct ip);
			icsend(mp, ifp, ip, ilen);
			netstat.ic_echoes++;
			return;

		case 11:		/* time exceeded */
			netstat.ic_timex++;
			break;

		case 13:		/* timestamp */

			/* reply by changing type, swapping addrs, and
			   adding r/t timestamps.  Timestamps are not UT
			   so high order bit is set. */

			if (icp->ic_code == 0) {
				icp->ic_type = 14;
				icp->ic_data.ic_time.trecv = 0x80000000 | 
							     (long)time;
				icp->ic_data.ic_time.ttrans = 0x80000000 | 
							      (long)time;
				temp = ip->ip_src;
				ip->ip_src = ip->ip_dst;
				ip->ip_dst = temp;
				icp->ic_sum = cksum(mp, ilen);
				mp->m_off -= sizeof(struct ip);
				mp->m_len += sizeof(struct ip);
				icsend(mp, ifp, ip, ilen);
			}
			return;

		case 15:		/* info request */

			/* reply by changing type, swapping addrs, and
			   filling in net part of addr */

			if (icp->ic_code == 0) {
				icp->ic_type = 16;
				temp = ip->ip_src;
				ip->ip_src.s_addr = ip->ip_dst.s_addr | 
						    (long)ifp->if_net;
				ip->ip_dst.s_addr = temp.s_addr | 
						    (long)ifp->if_net;
				icp->ic_sum = cksum(mp, ilen);
				mp->m_off -= sizeof(struct ip);
				mp->m_len += sizeof(struct ip);
				icsend(mp, ifp, ip, ilen);
			}
			return;
		}

		/* Dispatch messages to raw connections, if wanted */

		if (up != NULL && up->uc_flags&(UIP+URAW) &&
		    ((op == ICERR && up->uc_flags&RAWERR) || op == ICDAT)) {
			mp->m_off -= sizeof(struct ip);
			mp->m_len += sizeof(struct ip);
			raw_queue(mp, up);
		 } else
badret:
			netlog(mp);
	}
}

/*
 * Process GGP messages.
 */
ggp(mp, ifp)
register struct mbuf *mp;
struct ifcb *ifp;
{
	register struct ip *ip;
	register struct ggp *gp;
	register struct ucb *up;
	unsigned short i;
	int ilen;
	struct socket temp;
	struct ucb *icfind();

	ip = (struct ip *)((int)mp + mp->m_off);
	gp = (struct ggp *)((int)ip + sizeof(struct ip));

	ilen = ip->ip_len;
	
	switch (gp->gg_type) {

	case 3:		/* destination unreachable */
			
		if ((up = icfind(&gp->gg_data.gg_hdr)) != NULL)
			to_user(up, UNRCH);
		break;

	case 4:		/* source quench */

		up = icfind(&gp->gg_data.gg_hdr);
		netstat.ic_quenches++;
		break;
		
	case 5:		/* redirect */

		if ((up = icfind(&gp->gg_data.gg_redir.rhdr)) != NULL) {

		/* free the old route and insert the new */

			if (up->uc_route != NULL)
				h_free(up->uc_route);
			up->uc_route = h_make(&gp->gg_data.gg_redir.raddr);
			netstat.ic_redirects++;
		}
		break;

	case 8:		/* echo */

		/* reply to echo: swap src and dest addrs, change
		   type to zero, recompute checksum */

		gp->gg_type = 0;
		temp = ip->ip_src;
		ip->ip_src = ip->ip_dst;
		ip->ip_dst = temp;
		icsend(mp, ifp, ip, ilen);
		netstat.ic_echoes++;
		return;

	default:
		netstat.ic_drops++;
	}

	/* Dispatch messages to raw connections, if wanted */

	if (up != NULL && up->uc_flags&(UIP+URAW) &&
	    ((i == ICERR && up->uc_flags&RAWERR) || i == ICDAT)) 
		raw_queue(mp, up);
	else
		netlog(mp);
}

/*
 * Given pertinent fields from the original datagram header portion of
 * a GGP or ICMP packet, find the corresponding connection that originated
 * it (if any).
 */
struct ucb *icfind(hdr)
register struct th *hdr;
{
	register struct ucb *up;
	register struct tcb *tp;
	unsigned short src, dst;

	src = short_from_net(hdr->t_src);
	dst = short_from_net(hdr->t_dst);

	/* match ip/tcp header fields with open connection */

	for (up=contab; up < conNCON; up++) {

		if (up->uc_proc == NULL)
			continue;

		if (up->uc_flags & UTCP) {	/* tcp connection */
			
			if ((tp = up->uc_tcb) == NULL)
				continue;

			if (tp->t_lport == src &&
			    tp->t_fport == dst &&
			    up->uc_host.s_addr == hdr->t_d.s_addr &&
			    up->uc_srcif->if_addr.s_addr == hdr->t_s.s_addr &&
			    hdr->t_pr == TCPROTO)
				return(up);

		} else if (up->uc_flags & UIP) {	/* ip connection */

			if ((up->uc_host.s_addr == hdr->t_d.s_addr ||
			     up->uc_host.s_addr == NULL) &&
			    (up->uc_flags & RAWRALL ||
			     up->uc_srcif->if_addr.s_addr == hdr->t_s.s_addr) &&
			    hdr->t_pr >= up->uc_lo &&
			    hdr->t_pr <= up->uc_hi)
				return(up);

		} else if (up->uc_flags & URAW) {	/* raw connection */

			if ((up->uc_host.s_addr == hdr->t_d.s_addr ||
			     up->uc_host.s_addr == NULL) &&
			    (up->uc_flags & RAWRALL ||
			     up->uc_srcif->if_addr.s_addr == hdr->t_s.s_addr) &&
			    up->uc_lo <= IPLINK && up->uc_hi >= IPLINK)
				return(up);
		}
	}

	netstat.ic_drops++;
	return(NULL);	/* not found */
}

/*
 * Turn around an ICMP packet.
 */
icsend(mp, ifp, ip, ilen)
register struct mbuf *mp;
register struct ifcb *ifp;
register struct ip *ip;
register ilen;
{
	register hlen;
	struct socket temp;
	struct socket ip_route();

	/* fix ip header and recompute ip checksum */

	hlen = ip->ip_hl << 2;
	ilen += hlen;
	ip->ip_len = short_to_net(ilen);
	ip->ip_off = 0;
	ip->ip_id = short_to_net(ip->ip_id);
	ip->ip_sum = cksum(mp, hlen);

	/* pass packet to local net driver */

	temp = ip_route(&ip->ip_src, &ip->ip_dst);
	(*ifp->if_send)(mp, ifp, &temp, ilen, IPLINK, TRUE);
}