/* Copyright 1984 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in file notice.h */
#include	"notice.h"


/* This file contains routines to dispatch internet packets
 * to appropriate internet processing routines.  Some routines 
 * to handle and/or create internet packets are in this file.
 */

/* Fragmentation reassembly is done here.  (Moby EGP updates have forced
   the issue.) See RFC 815 for a description of the algorithm used. */

/*
 *------------------------------------------------------------------
 *
 * $Source: /mit/cgw/src/gw/in/RCS/inmux.c,v $
 * $Revision: 1.10 $
 * $Date: 89/03/05 22:58:22 $
 * $State: Exp $
 * $Author: jon $
 * $Locker: jon $
 *
 * $Log:	inmux.c,v $
 * Revision 1.10  89/03/05  22:58:22  jon
 * send icmp ttl exceeded messages
 * 
 * Revision 1.9  88/12/04  23:51:28  jon
 * add dispatch to snmp
 * 
 * Revision 1.8  88/06/08  15:49:50  jon
 * Use new test_gateway_flag instead of #ifdef'ing on TESTGW to decide
 * whether or not to send redirects.
 * 
 * Revision 1.7  88/03/28  17:01:02  jon
 * Also dispatch to rip_in on port RIPUDPSOCK2 to avoid other rip speakers.
 * 
 * Revision 1.6  87/10/27  23:51:51  jon
 * Fixes bugs in the reassembly code (threading of the linked list of buffers
 * and some timers stuff). 
 * 
 * Revision 1.5  87/10/08  21:45:27  jon
 * Supports reassembly.
 * 
 * Revision 1.4  87/09/18  04:36:53  jon
 * Removes abbreviations in messages, doesn't send redirects if TESTGW
 * is set at compile time, and dispatches to gw_control any incoming control
 * packets.
 * 
 * Revision 1.3  87/07/15  00:58:28  jon
 * Registers inflg with log_register_flag
 * 
 * Revision 1.2  87/06/24  14:16:37  jon
 * Initialization now initialized the rvd commands as well as the ip command.
 * 
 * 
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_inmux_c = "$Header: inmux.c,v 1.10 89/03/05 22:58:22 jon Locked $";
#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	"inudp.h"
#include	"inpro.h"

/* Error/info messages */

static char indns[] = "IN packet from %d.%d.%d.%d to %d.%d.%d.%d prot %d no server\n";
static char ggpec[] = "GGP echo from %d.%d.%d.%d to %d.%d.%d.%d\n";
static char ggperi[] = "GGP echo reply from %d.%d.%d.%d ignored\n";
static char ggprui[] = "GGP routing update from %d.%d.%d.%d ignored\n";
static char ggpnrui[] = "GGP new routing update from %d.%d.%d.%d ignored\n";
static char ggprdi[] = "GGP redirect from %d.%d.%d.%d ignored\n";
static char ggpai[] = "GGP ACK from %d.%d.%d.%d ignored\n";
static char ggpni[] = "GGP NACK from %d.%d.%d.%d ignored\n";
static char ggpbo[] = "GGP bad opcode %d from %d.%d.%d.%d ignored\n";
static char icmpec[] = "ICMP echo from %d.%d.%d.%d to %d.%d.%d.%d\n";
static char icmperi[] = "ICMP echo reply from %d.%d.%d.%d ignored\n";
static char icmpbo[] = "ICMP bad opcode %d from %d.%d.%d.%d ignored\n";
static char icmpnbo[] = "ICMP bad opcode `%s' from %d.%d.%d.%d ignored\n";
static char inpa[] = "IN cannot alloc. packet prot. %d\n";
static char innonet[] = "IN no net to %d.%d.%d.%d\n";
static char udpnosvr[] = "UDP port %d from hst %d.%d.%d.%d, no server\n";
static char udpbdck[] = "UDP bad cksum %04x port %d from %d.%d.%d.%d\n";

static	word	ident;			/* unique id for outgoing packets */

int n_frag_timers;  /* want to see this from DDT */

/* reassembly data structures */

#define hole   struct holestr

hole {
  unss    first;              /* first octet described by this hole */
  unss    last;               /* last octet described by this hole */
  hole    *next;              /* thread to next */
};

#define reassembly_buffer_descriptor struct rbdstr

reassembly_buffer_descriptor {
  reassembly_buffer_descriptor
          *next;                /* link to buffer descriptor */
  /* next three uniquely identify a reassembly buffer for a given fragment */
  inaddr  src;                /* source of packet */
  inaddr  dst;                /* destination */
  unsb    prot;               /* protocol */
  unsb    ttl;                /* ttl for fragment, when this expires punt */
  unss    id;                 /* fragment identification number */
  iorb    *iob;               /* pointer to iob (and thus reassembly buffer) */
  hole    hole_descriptor;    /* contains pointer to first hole descriptor */
};

reassembly_buffer_descriptor *reassembly_buffer_list;

/* IN protocol startup. Sets up some initial stuff, clears out the
 * routing tables and rebuilds them from device device interface info
 * and static routes, and then calls the per network protocol
 * initialization for networks that we know about.
 */

INNET()
{
/*	printf("INNET: started\n"); */
	nprots++;

	clrq(&inq);
	clrq(&inlpq);
	clrq(&inxq);

	initintabs(); /* inrte stuff */
	initrvdcommands ();
	log_register_flag ("in", &MSGFLG);

	reassembly_buffer_list = NULL;	/* no fragments yet */
	n_frag_timers = 0;
}

/* Handle packets on process input queue.
 */
	
inxmux()

{	while (tstneq(&inlpq)) {
/*		if (*gwmsgq != NULL) {
			addtsk(inhnd, INPPRI, &inxmux, 0);
			return;
			}	Should reimplement this */
		inmux(rmq(&inlpq));
		}
}


/* Top level for IP protocol services; basically waits to
 * be handed packets from the forwarding process.
 */

inmux(iob)
reg	iorb	*iob;
{
	reg	inpkt	*pkt;
        reg     unss    frgoff;

	pkt = mkin(iob->i_addr);
	frgoff = ip2s (pkt->i_fofflg);

	if (frgoff & (INMFRG|INFOFFM)) { /* non-zero offset or more frag bit */
	  infrag_in (iob);
	  freebuf (iob);
	  return;
	}

	switch(pkt->i_prot) {
	case INICMP:
	    inicmp(iob);
	    return;
	case INGGP:
	    inggp(iob);
	    return;
	case INEGP:
	    inegp(iob);
	    return;
	case INUDP:
	    inudp(iob);
	    return;
	case INTCP:
	    if (!(iob->i_stat & I_BRD))
	      tcp_Handler(iob);
	    freebuf(iob);
	    return;
	}

	niflog(L_ERRU)
		dolog(indns, mksina(&pkt->i_src), mksina(&pkt->i_dst),
						pkt->i_prot);
	freebuf(iob);
}


/* Deal with GGP packets.
 */

inggp(iob)
reg	iorb	*iob;

{	reg	inpkt	*pkt;
	reg	ggpkt	*gpkt;
		char	*str;
		unsw	level;
		long	tmp;
		void	inxfwd();

	pkt = mkin(iob->i_addr);
	gpkt = ((ggpkt *) (((char * ) pkt) +
				((pkt->i_ihlver & INHLENM) * INBPHW)));

	switch (gpkt->ggp_type) {

		case GGPECHO:	niflog(L_INFC)
					dolog(ggpec, mksina(&pkt->i_src),
						mksina(&pkt->i_dst));
				gpkt->ggp_type = GGPRPLY;
				tmp = pkt->i_dst.i_long;
				pkt->i_dst.i_long = pkt->i_src.i_long;
				pkt->i_src.i_long = tmp;
				inxaddq(iob);
				return;

		case GGPRPLY:	str = ggperi;
				level = L_ERRC;
				break;

		case GGPRU:	str = ggprui;
				level = L_INFC;
				break;

		case GGPNRU:	str = ggpnrui;
				level = L_INFC;
				break;

		case GGPRDR:	str = ggprdi;
				level = L_INFU;
				break;

		case GGPACK:	str = ggpai;
				level = L_INFC;
				break;

		case GGPNACK:	str = ggpni;
				level = L_INFC;
				break;

		default:	niflog(L_ERRU)
					dolog(ggpbo, gpkt->ggp_type,
						mksina(&pkt->i_src));
				freebuf(iob);
				return;
		}

	niflog(level)
		dolog(str, mksina(&pkt->i_src));
	freebuf(iob);
}


/* Deal with ICMP packets.
 */

inicmp(iob)
reg	iorb	*iob;

{	reg	inpkt	*pkt;
	reg	icmpkt	*ipkt;
		char	*str;
		unsw	level;
		long	tmp;
		void	inxfwd();
		unsw	len;
		unsw	hlen;

	pkt = mkin(iob->i_addr);
	hlen = ((pkt->i_ihlver & INHLENM) * INBPHW);
	len = swab(pkt->i_len) - hlen;
	ipkt = ((icmpkt *) (((char * ) pkt) + hlen));

	switch (ipkt->icmp_type) {
	case ICMPECHO:
	    niflog(L_INFC)
	      dolog(icmpec, mksina(&pkt->i_src),
		    mksina(&pkt->i_dst));
	    tmp = pkt->i_dst.i_long;
	    pkt->i_dst.i_long = pkt->i_src.i_long;
	    pkt->i_src.i_long = tmp;
	    ipkt->icmp_type = ICMPRPLY;
	    ipkt->icmp_cksum = 0;
	    ipkt->icmp_cksum = ~incksum(ipkt, len);
	    inxaddq(iob);
	    return;
	    
	case ICMPRPLY:	str = icmperi;
	    level = L_ERRC;
	    break;
	    
	default:
	    if (ipkt->icmp_type > max_icmp_opcode) {
		niflog(L_ERRU)
		  dolog(icmpbo, ipkt->icmp_type, mksina(&pkt->i_src));
	    }
	    else {
		niflog(L_ERRU)
		  dolog(icmpnbo, icmp_opcode_names[ipkt->icmp_type],
			mksina(&pkt->i_src));
	    }
	    freebuf(iob);
	    return;
	}

	niflog(level)
		dolog(str, mksina(&pkt->i_src));
	freebuf(iob);
}


/* Send an icmp redirect packet to the src host of the packet
 * being redirected. The rhdr field contains a pointer to the internet
 * header of the packet being redirected, to copy into the icmp
 * packet; the gate field is the redirect-to gateway.
 */

inredir(src, gate, rhdr)
inaddr	*src;				/* src host address */
inaddr	*gate;				/* redirect-to gateway */
inpkt	*rhdr;				/* Hdr of old pkt */

{	reg	icmpkt	*ipkt;
	reg	struct	icmprdr	*irdr;
		word	dlen;
		iorb	*iob;
		inpkt	*pkt;
	ext     bitf    test_gateway_flag;

	if (test_gateway_flag) return;

	if ((iob = mkiorb(getbuf(bufsiz))) == NULL) {
		niflog(L_ERRC)
			dolog(inpa, INICMP);
		return;
		}

	dlen = sizeof(icmpkt) + ((rhdr->i_ihlver & INHLENM) * INBPHW) + ICMPORTLEN;

	mkinpkt(iob, src, &rhdr->i_src, INICMP, dlen);

	pkt = mkin(iob->i_addr);
	ipkt = (icmpkt *) (((char *)pkt) +
			((pkt->i_ihlver & INHLENM) * INBPHW));

	irdr = ((struct icmprdr *) ipkt->icmp_data);

	ipkt->icmp_type = ICMPRDR;
	ipkt->icmp_code = ICMPRDRHOST;
	irdr->rdr_gate.i_long = gate->i_long;
	copy(rhdr, &irdr->rdr_pkt,
		(((rhdr->i_ihlver & INHLENM) * INBPHW) + ICMPORTLEN));

	ipkt->icmp_cksum = 0;
	ipkt->icmp_cksum = ~incksum(ipkt, dlen);

	inxaddq(iob);
}

/* Send destination unreachable packet */
inunreach(src, upkt, ucode)
	inaddr	*src;
reg	inpkt	*upkt;
	unsw	ucode;
{
		iorb	*iob;
		word	dlen;
		inpkt	*pkt;
	reg	icmpkt	*ipkt;
	reg	struct icmpdur	*idur;


	if ((iob = mkiorb(getbuf(bufsiz))) == NULL) {
		niflog(L_ERRC)
			dolog(inpa, INICMP);
		return;
		}

	dlen = sizeof(icmpkt) + ((upkt->i_ihlver & INHLENM) * INBPHW) + ICMPORTLEN;

	mkinpkt(iob, src, &upkt->i_src, INICMP, dlen);

	pkt = mkin(iob->i_addr);
	ipkt = (icmpkt *) (((char *)pkt) +
			((pkt->i_ihlver & INHLENM) * INBPHW));

	idur = ((struct icmpdur *) ipkt->icmp_data);

	ipkt->icmp_type = ICMPDUR;
	ipkt->icmp_code = ucode;
	idur->dur_blank = 0;
	copy(upkt, &idur->dur_pkt,
		(((upkt->i_ihlver & INHLENM) * INBPHW) + ICMPORTLEN));

	ipkt->icmp_cksum = 0;
	ipkt->icmp_cksum = ~incksum(ipkt, dlen);

	inxaddq(iob);
}

icmp_ttl_exceeded(src, thdr, ttl_or_frag)
inaddr	*src;				/* src host address */
inpkt	*thdr;				/* Hdr of old pkt */
unsb    ttl_or_frag;                    /* ICMPTTTLE or ICMPTFRGE */

{	reg	icmpkt	*ipkt;
	reg	struct	icmpttle *ittle;
		word	dlen;
		iorb	*iob;
		inpkt	*pkt;

	if ((iob = mkiorb(getbuf(bufsiz))) == NULL) {
		niflog(L_ERRC)
			dolog(inpa, INICMP);
		return;
		}

	dlen = sizeof(icmpkt) + ((thdr->i_ihlver & INHLENM) * INBPHW) + ICMPORTLEN;

	mkinpkt(iob, src, &thdr->i_src, INICMP, dlen);

	pkt = mkin(iob->i_addr);
	ipkt = (icmpkt *) (((char *)pkt) +
			((pkt->i_ihlver & INHLENM) * INBPHW));

	ittle = ((struct icmpttle *) ipkt->icmp_data);

	ipkt->icmp_type = ICMPTTLE;
	ipkt->icmp_code = ttl_or_frag;
	copy(thdr, &ittle->ttl_pkt,
		(((thdr->i_ihlver & INHLENM) * INBPHW) + ICMPORTLEN));

	ipkt->icmp_cksum = 0;
	ipkt->icmp_cksum = ~incksum(ipkt, dlen);

	inxaddq(iob);
}

/* Make an internet packet */
mkinpkt(iob, src, dst, prot, len)
reg	iorb	*iob;
	inaddr	*src;
	inaddr	*dst;
	byte	prot;
	unsw	len;
{	
	reg	inpkt	*pkt;

	len += sizeof(inpkt);

	iob->i_addr = ((byte *)iob) + pktoff;
	iob->i_breq = len;


	pkt = mkin(iob->i_addr);
	pkt->i_ihlver = INHVER | INMINH;
	pkt->i_tos = INTOS;		/* no type-of-service */
	pkt->i_len = swab(len);
	pkt->i_id = ++ident;		/* get unique id */
	pkt->i_fofflg = 0;
	pkt->i_ttl = INTTL;
	pkt->i_prot = prot;
	pkt->i_cksum = 0;
	pkt->i_src.i_long = src->i_long;
	pkt->i_dst.i_long = dst->i_long;
	pkt->i_cksum = ~cksum(pkt, INMINH * INWPHW, 0);
	return;
}

/* Return internet packet to src host. 
 The time to live field is not refilled. (????)
*/
mkrinpkt(iob, len)
reg	iorb	*iob;
unsw		len;
{
	reg	inpkt	*pkt;
		unsw	iphlen;
		long	tmp;

	pkt = mkin(iob->i_addr);
	iphlen = ((pkt->i_ihlver & INHLENM) * INBPHW);
	len += iphlen;

	iob->i_breq = len;

	pkt->i_ttl = INTTL; 
	pkt->i_len = swab(len);
	tmp = pkt->i_dst.i_long;
	pkt->i_dst.i_long = pkt->i_src.i_long;
	pkt->i_src.i_long = tmp;
	pkt->i_cksum = 0;
	pkt->i_cksum = ~cksum(pkt, iphlen >> 1, 0);
	return;
}

incksum(ppkt, len)
unsb	*ppkt;
unsw	len;
{
	unsw	chksum;

	if (len & 01) {
		*(ppkt + len) = 0;
		len++;
		}

	chksum = cksum(ppkt, (len >> 1), 0);
	return(chksum);
}

/* Demuxes UDP packets to their appropriate destination if one exists.
 */
inudp(iob)
iorb	*iob;
{
    reg	udppkt	*pkt;
    inpkt *ipkt;
    int check;
    int req;

    ipkt = mkin(iob->i_addr);
    pkt = mkudp((byte *)ipkt + sizeof(inpkt));
    if (pkt->u_chk != 0) {
	check = (ints)pkt->u_chk;
	pkt->u_chk = 0;
	if (check != udp_cksum(ipkt)) {
	    niflog(L_ERRU)
	      dolog(udpbdck, check&0xffff, swab(pkt->u_dst),
		    mksina(&ipkt->i_src));
	    freebuf(iob);
	    return;
	}
    }
	
    switch (swab(pkt->u_dst)) {
    case BOOTP_SERVER:
	bootp_in(iob);
	break;
    case RIPUDPSOCK:
	rip_in(iob);
	break;
    case RIPUDPSOCK2:
	rip_in(iob);
	break;
    case GWSTATSOCK:
	niflog(L_TRCO)
	  dolog("Status request from %d.%d.%d.%d\n", mksina(&ipkt->i_src));
	in_send_stat(iob);
	break;
    case GW_CONTROL:
	gw_control (iob);
	break;
    case SNMP_PORT:
	snmp_in (iob);
	break;
    case TIME_PORT:
	time_in (iob);
	break;
    case NTP_PORT:
	ntp_in (iob);
	break;
    default:
	niflog(L_INFC)
	  dolog(udpnosvr, swab(pkt->u_dst), mksina(&ipkt->i_src));
	freebuf(iob);
    }
}

/* Computes and returns the checksum for a udp packet.  This routine
 * should be passed a pointer to an internet packet, not a udp packet.
 * The information in the internet header is needed to create the udp
 * pseudo header. */
udp_cksum(ipkt)
reg inpkt *ipkt;
{
    reg udppkt *upkt;
    struct udp_ph ph;
    int check;

    upkt = mkudp((byte *)ipkt + sizeof(inpkt));
    ph.uph_src = ipkt->i_src.i_long;
    ph.uph_dst = ipkt->i_dst.i_long;
    ph.uph_zero = 0;
    ph.uph_prot = ipkt->i_prot;
    ph.uph_len = upkt->u_len;
    ((char *)upkt)[swab(upkt->u_len)] = 0;
    check = cksum(&ph, sizeof(ph)/sizeof(unss), 0);
    check = ~cksum(upkt, (swab(upkt->u_len) + 1)/sizeof(unss), check);
    if (check == 0)
      return ~0;
    else
      return check;
}


mkhole (r, new_first, new_last)
     reassembly_buffer_descriptor *r;
     unss new_first, new_last;
{
  hole *tmp, *new_hole;

  new_hole = (hole *) r->iob->i_addr + sizeof (inpkt) + new_first;
  new_hole->first = new_first;
  new_hole->last = new_last;

  tmp = r->hole_descriptor.next;
  r->hole_descriptor.next = new_hole;
  new_hole->next = tmp;  
}

infrag_free_buff (r)
     reassembly_buffer_descriptor *r;
{
     reassembly_buffer_descriptor *prev, *next;

     if (r->iob != NULL) freebuf (r->iob);

     /* special case head */
     if (reassembly_buffer_list == r) {
       reassembly_buffer_list = r->next;
       free (r);
       niflog (L_TRCO)
	 dolog ("IN frag freed head of list, new head = %x\n",
		reassembly_buffer_list);
       return;
     }
       
     for (prev = reassembly_buffer_list; prev != NULL; prev = prev->next)
       if (prev->next == r) break;

     if (prev == NULL) { /* this is the only buffer */
       free (r);
       reassembly_buffer_list = NULL;
       niflog (L_TRCO)
	 dolog ("IN frag freed only frag reassembly buffer.\n");
       return;
     }
     else {
       niflog (L_TRCO)
	 dolog ("IN frag freed reassembly buffer %x, next = %x, prev = %x, head = %x.\n", 
		r, r->next, prev, reassembly_buffer_list);
       prev->next = r->next;
       free (r);
     }
   }

infrag_gc ()
{
  reassembly_buffer_descriptor *r;
  
  for (r = reassembly_buffer_list; r != NULL; r = r->next)
    if (--(r->ttl) == 0) {
      niflog (L_ERRU)
	dolog ("IN frag GC ttl expired from %d.%d.%d.%d\n", mksina (&r->src));
      infrag_free_buff (r);
    }
  if (reassembly_buffer_list == NULL) {
    n_frag_timers--;
    niflog (L_TRCO)
	dolog ("IN frag GC resetting timer (%d).\n", n_frag_timers);
  }
  else {
    if (--n_frag_timers == 0) {
      n_frag_timers++;
      niflog (L_TRCO)
	dolog ("IN frag GC setting timer again (%d).\n", n_frag_timers);
      stime (infrag_gc, NULL, 1 /* one second */, INFRAGPRI);       
    }
    else
      niflog (L_TRCO)
	dolog ("In frag GC not setting timer, already %d timers.\n",
	       n_frag_timers); /* hopefully this will be one ... */
  }
}

/* Process an incoming fragment ...

   A new iob is allocated for reassembly and the old one is freed.  This
   means the max size that can be handled is defined by pktsiz.  Might want
   a way to bump the this size without bumping all iob sizes, but this will
   do for now.

   I don't yet (if ever deal with IP options) so the ip header present 
   in the reassembled packet is always sizeof (inpkt) long
*/

infrag_in (iob)
     iorb *iob;
{
  reassembly_buffer_descriptor *r, *temp_r;
  hole *h, *prev_hole;
  unss frag_first, frag_last;  /* first and last octet in fragment */
  unss hlen, dlen;
  int more_frags;
  inpkt *pkt;
  unss frgoff;

  pkt = mkin(iob->i_addr);
  frgoff = ip2s (pkt->i_fofflg);

  /* need hlen to make first hole (this will lose with options!) */
  hlen = (pkt->i_ihlver & INHLENM) * INBPHW;
  dlen = ip2s (pkt->i_len) - hlen;

  for (r = reassembly_buffer_list; r != NULL; r = r->next) {
    if ((pkt->i_src.i_long == r->src.i_long) &&
	(pkt->i_dst.i_long == r->dst.i_long) &&
	(pkt->i_prot == r->prot) &&
	(pkt->i_id == r->id))
      break;
  }

  if (r == NULL) {
    /* have to allocate a new buffer and descriptor */

    if (nbfree <= buflow) { /* a small attempt at not being anti-social */
      niflog (L_ERRC)
	dolog ("IN not enough buffers, frag from %d.%d.%d.%d dsc\n",
	       mksina (&pkt->i_src));
      return;
    }

    r = (reassembly_buffer_descriptor *)
      alloc (sizeof (reassembly_buffer_descriptor));
    if (r == NULL) {
      niflog (L_ERRC)
	dolog
	  ("IN couldn't alloc reassembly bufd, frag from %d.%d.%d.%d disc\n",
	       mksina (&pkt->i_src));
      return;
    }

    if ((r->iob = getbuf()) == NULL) {
	niflog(L_ERRU)
	  dolog("IN couldn't alloc buf for reassembly\n");
    }
    r->iob->i_addr = ((byte *)r->iob) + pktoff;

    /* fill in info from fragment */
    r->src.i_long = pkt->i_src.i_long;
    r->dst.i_long = pkt->i_dst.i_long;
    r->prot = pkt->i_prot;
    r->id = pkt->i_id;
    r->ttl = INTTL; /* let's use our idea ... */

    /* set up first hole descriptor */
    h = (hole *) r->iob->i_addr + hlen;
    h->first = 0;
    h->last = pktsiz - hlen; /* last valid data position */
    h->next = &(r->hole_descriptor);
    r->hole_descriptor.next = h;

    /* thread into buffer descriptor list */
    if (reassembly_buffer_list == NULL) {
       /* first buffer created ... */
      reassembly_buffer_list = r; 
      r->next = NULL;
    }
    else {
      temp_r = reassembly_buffer_list->next;
      reassembly_buffer_list->next = r;
      r->next = temp_r;
    }

    niflog (L_TRCO) 
      dolog ("IN allocated reassembly buffer, src %d.%d.%d.%d, prot %d\n",
	     mksina (&pkt->i_src), pkt->i_prot);
  }

  /* the correct buffer descriptor is in r ... get down to work ... */

  frag_first = (frgoff & INFOFFM) << 3;   /* measure in 8 byte chunks */
  frag_last = frag_first + dlen;
  more_frags = ((frgoff & INMFRG) != 0);

  niflog (L_INFU) /* unusual normal event */
    dolog ("IN fragment from %d.%d.%d.%d, prot %d, off %d, len %d, mf %d\n",
	   mksina (&pkt->i_src), pkt->i_prot, frag_first, dlen, more_frags);

  for (h = r->hole_descriptor.next; h != &(r->hole_descriptor); h = h->next) {
    if ((frag_first > h->last) || (frag_last < h->first))
      continue; 

    /* get rid of current hole */
    for (prev_hole = r->hole_descriptor.next; 
	 prev_hole != & (r->hole_descriptor);
	 prev_hole = prev_hole->next)
      if (prev_hole->next == h) break;
    prev_hole->next = h->next;

    /* make new hole descriptors as appropriate */
    if (frag_first > h->first) mkhole (r, h->first, frag_first);
    if ((frag_last < h->last) && more_frags)
      mkhole (r, frag_last + 1, h->last);
  }
  
  /* now copy new fragment data into buffer */

  copy ((byte *) pkt + hlen, r->iob->i_addr + frag_first + hlen, dlen);

  if (!more_frags) {
    pkt->i_fofflg = 0;
    pkt->i_len = s2ip (frag_first + dlen + hlen);
    copy ((byte *) pkt, r->iob->i_addr, hlen);
  }
    
  if (r->hole_descriptor.next == &(r->hole_descriptor)) { 
    /* no more holes, time to dispatch */
    niflog (L_INFU)
      dolog ("IN pkt reassembled: from %d.%d.%d.%d, len %d, prot %d\n",
	     mksina (&pkt->i_src), ip2s (pkt->i_len), pkt->i_prot);
    inmux (r->iob);
    r->iob = NULL;  /* inmux has freed it already */
    infrag_free_buff (r);
  }
  else {
    /* can expect more fragments ... set up GC timer, unless we already have */
    /* I don't think this can happen but I saw it during debugging at least
       once, so I'm going to play it safe */
    if (n_frag_timers < 0) {
      niflog (L_ERRU)
	dolog ("IN frag n_frag_timers < 0, reset to 0.\n");
      n_frag_timers == 0; /* hmm ... */
    }
    if (n_frag_timers == 0) {
      n_frag_timers++;
      niflog (L_TRCO)
	dolog ("IN frag setting first timer (%d).\n", n_frag_timers);
      stime (infrag_gc, NULL, 1 /* one second */, INFRAGPRI); 
    }
  }
}

#ifdef NOTDEF
dump_reassembly_buffs_cmd (devp, arg, jcl)
dct *devp;
int arg;
char *jcl;
{
  reassembly_buffer_descriptor *r;

  fprintf (devp, "Head = %x, n_frag_timers = %d.\n",
	   reassembly_buffer_list, n_frag_timers);

  for (r = reassembly_buffer_list; r != NULL; r = r->next)
    fprintf (devp, "r = %x, next = %x, src = %d.%d.%d.%d, ttl %d, iob %x.\n",
	     r, r->next, mksina (&r->src), r->ttl, r->iob);
}

dump_in_pkt (pkt)
     inpkt *pkt;
{
  dolog ( "IN pkt (reassem): src %d.%d.%d.%d, dst %d.%d.%d.%d, ver %d, hlen %d, tos %d\n",
	 mksina (&pkt->i_src), mksina (&pkt->i_dst), pkt->i_ihlver & INHVERM,
	 pkt->i_ihlver & INHLENM, pkt->i_tos);

  dolog ("                  len %d, ttl %d, prot %d, chksum %d\n", ip2s (pkt->i_len),
	 pkt->i_ttl, pkt->i_prot, ip2s (pkt->i_cksum));
}
#endif NOTDEF
