ftp.nice.ch/pub/next/unix/network/system/gated.2.1pl2.NI.bs.tar.gz#/gated-2.1/src/krt.c

This is krt.c in view mode; [Download] [Up]

/*
 *  $Header: /disk/d/src/devel/gated/dist/src/RCS/krt.c,v 2.1 92/02/24 14:12:41 jch Exp $
 */

/*%Copyright%*/
/************************************************************************
*									*
*	GateD, Release 2						*
*									*
*	Copyright (c) 1990,1991,1992 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		*
*	2 in whole or in part may be obtained by writing to:		*
*									*
*	    GateDaemon Project						*
*	    Information Technologies/Network Resources			*
*	    143 Caldwell Hall						*
*	    Cornell University						*
*	    Ithaca, NY 14853-2602					*
*									*
*	GateD is based on Kirton's EGP, UC Berkeley's routing		*
*	daemon	 (routed), and DCN's HELLO routing Protocol.		*
*	Development of Release 2 has been supported by the		*
*	National Science Foundation.					*
*									*
*	Please forward bug fixes, enhancements and questions to the	*
*	gated mailing list: gated-people@gated.cornell.edu.		*
*									*
*	Authors:							*
*									*
*		Jeffrey C Honig <jch@gated.cornell.edu>			*
*		Scott W Brim <swb@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.		*
*									*
************************************************************************/


/* krt.c
 *
 * Kernel routing table interface routines
 */

#define ROUTE_KERNEL
#include "include.h"
#include <nlist.h>
#ifdef	SYSV
#include <sys/sioctl.h>
#include <sys/stropts.h>
#else	/* SYSV */
#include <sys/ioctl.h>
#endif	/* SYSV */
#include <sys/mbuf.h>
#ifndef vax11c
#include <sys/file.h>
#endif				/* vax11c */
#ifdef	RTM_ADD
#include <sys/kinfo.h>
#endif				/* RTM_ADD */

#if	RT_N_MULTIPATH != 1
Fatal error - the BSD Unix kernel can only support one next hop !
#endif

extern off_t lseek();

task *krt_task;				/* Task for kernel routing table */

#ifndef	RTM_ADD
static struct nlist *krt_rt[2];

#define	KRT_RTHOST	0
#define	KRT_RTNET	1
static struct nlist *krt_rthashsize;

#endif				/* RTM_ADD */
#ifndef	vax11c
static struct nlist *krt_version;

#else				/* vax11c */
static struct nlist *krt_multinet_version;
static struct nlist *krt_multinet_product_name;

#define read(kmem,buf,size)	klread(buf,size)
#define lseek(kmem,offset,how)	klseek(offset)
#endif				/* vax11c */

static struct {
    const char *nl_name;
    struct nlist **nl_ptr;
} nl_names[] = {

#ifndef	RTM_ADD
    {
	"_rthost", &krt_rt[KRT_RTHOST]
    },
    {
	"_rtnet", &krt_rt[KRT_RTNET]
    },
    {
	"_rthashsize", &krt_rthashsize
    },
#endif				/* RTM_ADD */
#ifndef	vax11c
    {
	"_version", &krt_version
    },
#else				/* vax11c */
    {
	"_multinet_version", &krt_multinet_version
    },
    {
	"_multinet_product_name", &krt_multinet_product_name
    },
#endif				/* vax11c */
    {
	NULL, NULL
    }
};

#define	NL_SIZE	(sizeof (nl_names)/sizeof (nl_names[0]))

#if	defined(ULTRIX3_X) || defined(ULTRIX4_X) || defined(hpux)
typedef struct rtentry krt_type;

#define	krt_next	rt_next
#define	krt_size	sizeof(krt_type)
#define	krt_conv(ptr)	(&ptr)
#endif

#if	defined(SYSV)
typedef	struct msgb	krt_type;

#define	krt_next	b_next;
#define	krt_size	sizeof(krt_type)
#endif

#if	!defined(krt_next) && !defined(RTM_ADD)
typedef struct mbuf krt_type;

#define	krt_next	m_next
#define	krt_size	(MMINOFF + sizeof(struct rtentry))
#define krt_conv(ptr)	mtod(&ptr, struct rtentry *)
#endif				/* !defined(krt_next) && !defined(RTM_ADD) */


 /*	Delete a route given dest, gateway and flags	*/
/*ARGSUSED*/
int
krt_delete_dst(tp, dest, mask, gate, flags)
task *tp;
sockaddr_un *dest;
sockaddr_un *mask;
sockaddr_un *gate;
flag_t flags;
{
    int do_ioctl = !test_flag && install;
    struct rtentry krt;

    memset((caddr_t) & krt, (char) 0, sizeof(krt));
    sockcopy(dest, &krt.rt_dst);
    sockcopy(gate, &krt.rt_gateway);
    krt.rt_flags = flags;

    if (do_ioctl && (task_ioctl(tp->task_socket, SIOCDELRT, (caddr_t) & krt, sizeof (krt)) == -1)) {
	trace(TR_ALL, LOG_ERR, "krt_delete_dst: task: %s: SIOCDELRT %A via %A flags <%s>: %m",
	      task_name(tp),
	      &krt.rt_dst,
	      &krt.rt_gateway,
	      trace_bits(rt_flag_bits, krt.rt_flags));
	return (1);
    }
    return (0);
}


#if	defined(krt_next)
 /*  Read the kernel's routing table.			*/
static void
krt_rtread(kmem)
int kmem;
{
    int saveinstall = install;
    int i, hashsize = 0, rtbufsize, krt_table;
    flag_t table;
    register if_entry *ifp;
    struct rtentry *krt;
    krt_type *next, m_buf, **base;

    if (kmem < 0) {
	return;
    }

    /* Make sure we have the addresses of the hash tables */
    if ((krt_rt[KRT_RTHOST]->n_value == 0) || (krt_rt[KRT_RTNET]->n_value == 0)) {
	trace(TR_ALL, LOG_ERR, "krt_rtread: rthost and/or rtnet not in namelist");
	quit(errno);
    }

    /* Get the number of hash buckets */
    if (krt_rthashsize->n_value != 0) {
	(void) lseek(kmem, (off_t) krt_rthashsize->n_value, 0);
	(void) read(kmem, (caddr_t) & hashsize, sizeof(hashsize));
    }
    if (!hashsize) {
#ifdef	RTHASHSIZ
	trace(TR_ALL, 0, "krt_rtread: defaulting rthashsize to RTHASHSIZ(%d)",
	      hashsize = RTHASHSIZ);
#else				/* RTHASHSIZ */
	trace(TR_ALL, LOG_ERR, "krt_rtread: rthashsize not in namelist");
	quit(ENOENT);
#endif				/* RTHASHSIZ */
    }

    /* set up to read the table of hash chains */
    rtbufsize = hashsize * sizeof(krt_type *);
    base = (krt_type **) malloc((unsigned int) rtbufsize);
    if (base == NULL) {
	trace(TR_ALL, LOG_ERR, "krt_rtread: malloc: %m");
	quit(errno);
    }
    for (krt_table = KRT_RTHOST; krt_table <= KRT_RTNET; krt_table++) {
#ifdef	_IBMR2
	off_t root;

	if (lseek(kmem, (off_t) krt_rt[krt_table]->n_value, 0) == -1) {
	    trace(TR_ALL, LOG_ERR, "krt_rtread: lseek rthash: %m");
	    quit(errno);
	}
	if (read(kmem, (caddr_t) &root, sizeof (root)) != sizeof (root)) {
	    trace(TR_ALL, LOG_ERR, "krt_rtread: read rthash: %m");
	    quit(errno);
	}
#else	/* _IBMR2 */
#define	root	krt_rt[krt_table]->n_value
#endif	/* _IBMR2 */

	if (lseek(kmem, (off_t) root, 0) == -1) {
	    trace(TR_ALL, LOG_ERR, "krt_rtread: lseek rthash: %m");
	    quit(errno);
	}
	if (read(kmem, (caddr_t) base, rtbufsize) != rtbufsize) {
	    trace(TR_ALL, LOG_ERR, "krt_rtread: read rthash: %m");
	    quit(errno);
	}
	for (i = 0; i < hashsize; i++) {
	    for (next = base[i]; next != NULL; next = m_buf.krt_next) {
#ifdef	SYSV
		struct rtentry krt_rtentry;
#endif	/* SYSV */

		if (lseek(kmem, (off_t) next, 0) == -1) {
		    trace(TR_ALL, LOG_ERR, "krt_rtread: lseek rtentry: %m");
		    quit(errno);
		}
		if (read(kmem, (caddr_t) & m_buf, krt_size) != krt_size) {
		    trace(TR_ALL, LOG_ERR, "krt_rtread: read rtentry: %m");
		    quit(errno);
		}
#ifdef	SYSV
		if (lseek(kmem, (off_t) m_buf.b_rptr, 0) == -1) {
		    trace(TR_ALL, LOG_ERR, "krt_rtread: lseek msg->rtentry: %m");
		    quit(errno);
		}
		if (read(kmem, (caddr_t) & krt_rtentry, sizeof (krt_rtentry)) != sizeof (krt_rtentry)) {
		    trace(TR_ALL, LOG_ERR, "krt_rtread: read msg->rtentry: %m");
		    quit(errno);
		}
		krt = &krt_rtentry;
#else	/* SYSV */
		krt = krt_conv(m_buf);
#endif	/* SYSV */

		if (krt->rt_gateway.sa_family != AF_INET) {
		    continue;
		}
		install = FALSE;	/* don't install routes in kernel */

		if (krt->rt_flags & RTF_HOST) {
		    table = RTS_HOSTROUTE;
		} else {
		    /*
	             *	Route is interior if we have an interface to it or a subnet of it
	             */
		    table = RTS_EXTERIOR;
		    IF_LIST(ifp) {
			if (gd_inet_wholenetof(socktype_in(&krt->rt_dst)->sin_addr)
			    == gd_inet_wholenetof(ifp->int_addr.in.sin_addr)) {
			    table = RTS_INTERIOR;
			    break;
			}
		    } IF_LISTEND(ifp) ;
		}

		/*
	         *	If Kernel route already exists, delete this one, the kernel uses the
	         *	first one
	         */
		if (rt_locate(table, (sockaddr_un *) & krt->rt_dst, RTPROTO_KERNEL)) {
		    goto Delete;
		}
		/*
	         *	If there was a problem adding the route, delete the kernel route
	         */
		if (!rt_add((sockaddr_un *) & krt->rt_dst,
			    (sockaddr_un *) 0,
			    (sockaddr_un *) & krt->rt_gateway,
			    (gw_entry *) 0,
			    0,
			    table,
			    RTPROTO_KERNEL,
			    0,
			    (time_t) 0,
			    RTPREF_KERNEL)) {
		    goto Delete;
		}
		continue;

	      Delete:
		install = saveinstall;
		krt_delete_dst(krt_task,
			       (sockaddr_un *) & krt->rt_dst,
			       (sockaddr_un *) 0,
			       (sockaddr_un *) & krt->rt_gateway,
			       (flag_t) krt->rt_flags);
	    }
	}
    }
    free((caddr_t) base);

    install = saveinstall;
    
    return;
}

#endif				/* defined(krt_next) */


#ifdef	RTM_ADD

/* Support for BSD4.4 route socket. */

#define	KRT_TIMEOUT	2		/* Length of time before a response is overdue */

#define ROUNDUP(a) (1 + (((a) - 1) | (sizeof(long) - 1)))

static bits rtm_type_bits[] =
{
    {RTM_ADD, "ADD"},
    {RTM_DELETE, "DELETE"},
    {RTM_CHANGE, "CHANGE"},
    {RTM_GET, "GET"},
    {RTM_LOSING, "LOSING"},
    {RTM_REDIRECT, "REDIRECT"},
    {RTM_MISS, "MISS"},
    {RTM_LOCK, "LOCK"},
    {RTM_OLDADD, "OLDADD"},
    {RTM_OLDDEL, "OLDDEL"},
#ifdef	RTM_RESOLVE
    {RTM_RESOLVE, "RESOLVE"},
#endif				/* RTM_RESOLVE */
};

static bits rtm_lock_bits[] =
{
    {RTV_MTU, "MTU"},
    {RTV_HOPCOUNT, "HOPCOUNT"},
    {RTV_EXPIRE, "EXPIRE"},
    {RTV_RPIPE, "RPIPE"},
    {RTV_SPIPE, "SPIPE"},
    {RTV_SSTHRESH, "SSTHRESH"},
    {RTV_RTT, "RTT"},
    {RTV_RTTVAR, "RTTVAR"},
};

static bits rtm_sock_bits[] =
{
    {RTA_DST, "DST"},
    {RTA_GATEWAY, "GATEWAY"},
    {RTA_NETMASK, "NETMASK"},
    {RTA_GENMASK, "GENMASK"},
    {RTA_IFP, "IFP"},
    {RTA_IFA, "IFA"},
    {RTA_AUTHOR, "AUTHOR"}
};

struct rtm_msg {
    struct rtm_msg *rtm_forw;
    struct rtm_msg *rtm_back;
    /* How about some statistics */
    struct rt_msghdr msghdr;
};

static struct rtm_msg rtm_head =
{&rtm_head, &rtm_head};			/* Head of message queue */

/* Trace a route socket packet */
/*ARGSUSED*/
static void
krt_trace(tp, direction, rtp)
task *tp;
char *direction;
struct rt_msghdr *rtp;
{
    sockaddr_un *ap;

    /* XXX - print minimal information, more if TR_UPDATE enabled */
    tracef("KRT %s  length %d  version %d  type %s(%d)  addrs %s(%x)  pid %d  seq %d  error %d",
	   direction,
	   rtp->rtm_msglen,
	   rtp->rtm_version,
	   trace_state(rtm_type_bits, rtp->rtm_type - 1),
	   rtp->rtm_type,
	   trace_bits(rtm_sock_bits, rtp->rtm_addrs),
	   rtp->rtm_addrs,
	   rtp->rtm_pid,
	   rtp->rtm_seq,
	   rtp->rtm_errno);
    if (rtp->rtm_errno) {
	errno = rtp->rtm_errno;
	trace(TR_KRT, 0, "%m");
    } else {
	trace(TR_KRT, 0, NULL);
    }

    tracef("KRT %s  flags %s(%x)",
	   direction,
	   trace_bits(rt_flag_bits, rtp->rtm_flags),
	   rtp->rtm_flags);
    tracef("  locks %s(%x)",
	   trace_bits(rtm_lock_bits, rtp->rtm_rmx.rmx_locks),
	   rtp->rtm_rmx.rmx_locks);
    trace(TR_KRT, 0, "  inits %s(%x)",
	  trace_bits(rtm_lock_bits, rtp->rtm_inits),
	  rtp->rtm_inits);

    /* Display metrics */
    switch (rtp->rtm_type) {
	case RTM_ADD:
	case RTM_CHANGE:
	case RTM_GET:
	    trace(TR_KRT, 0, "KRT %s  mtu %d  hopcount %d  expire %d  ssthresh %d",
		  direction,
		  rtp->rtm_rmx.rmx_mtu,
		  rtp->rtm_rmx.rmx_hopcount,
		  rtp->rtm_rmx.rmx_expire,
		  rtp->rtm_rmx.rmx_ssthresh);
	    trace(TR_KRT, 0, "KRT %s  recvpipe %d  sendpipe %d  rtt %d  rttvar %d",
		  direction,
		  rtp->rtm_rmx.rmx_recvpipe,
		  rtp->rtm_rmx.rmx_sendpipe,
		  rtp->rtm_rmx.rmx_rtt,
		  rtp->rtm_rmx.rmx_rttvar);
	    break;
    }
    ap = (sockaddr_un *) (rtp + 1);

    /* Display addresses */
    if (rtp->rtm_addrs & RTA_DST) {
	tracef("KRT %s  dest %A",
	       direction,
	       ap);
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_addrs & RTA_GATEWAY) {
	tracef("  next hop %A",
	       ap);
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_addrs & RTA_NETMASK) {
	tracef("  mask %A",
	       ap);
	if (ap->a.sa_len) {
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	} else {
	    ap = (sockaddr_un *) ((caddr_t) ap + sizeof(u_long));
	}
    }
    if (rtp->rtm_addrs & RTA_GENMASK) {
	tracef("  genmask %A", ap);
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_addrs & RTA_IFP) {
	tracef("  ifp %A", ap);
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_addrs & RTA_IFA) {
	tracef("  ifa %A", ap);
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_addrs & RTA_AUTHOR) {
	tracef("  author %A",
	       ap);
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    trace(TR_KRT, 0, NULL);

    trace(TR_KRT, 0, NULL);
}


/* Use the getkinfo() system call to read the routing table(s) */
/*ARGSUSED*/
static void
krt_rtread(kmem)
int kmem;
{
    int saveinstall = install;
    int size;
    caddr_t kbuf, cp, limit;
    struct rt_msghdr *rtp;
    flag_t table;
    if_entry *ifp;
    sockaddr_un *ap;
    sockaddr_un *dest = (sockaddr_un *) 0;
    sockaddr_un *mask = (sockaddr_un *) 0;
    sockaddr_un *gate = (sockaddr_un *) 0;

    if ((size = getkerninfo(KINFO_RT_DUMP, (caddr_t) 0, (int *) 0, 0)) < 0) {
	trace(TR_ALL, LOG_ERR, "krt_rtread: getkerninfo(KINFO_RT_DUMP) estimate: %m");
	quit(errno);
    }
    if (trace_flags & TR_PROTOCOL) {
	trace(TR_KRT, 0, "krt_rtread: getkerninfo(KINFO_RT_DUMP) estimates %d bytes needed",
	      size);
    }
    kbuf = (caddr_t) malloc(size);
    if (!kbuf) {
	trace(TR_ALL, LOG_ERR, "krt_rtread: malloc(%d) failed",
	      size);
	quit(ENOMEM);
    }
    if (getkerninfo(KINFO_RT_DUMP, kbuf, &size, 0) < 0) {
	trace(TR_ALL, LOG_ERR, "krt_rtread: getkerninfo(KINFO_RT_DUMP): %m");
	quit(errno);
    }
    limit = kbuf + size;

    for (cp = kbuf; cp < limit; cp += rtp->rtm_msglen) {
	sockaddr_un addr;

	rtp = (struct rt_msghdr *) cp;

	if (rtp->rtm_version != RTM_VERSION) {
	    trace(TR_ALL, LOG_ERR, "krt_rtread: version mismatch!  Expected %d, received %d",
		  RTM_VERSION,
		  rtp->rtm_version);
	    quit(EPROTONOSUPPORT);
	}
	krt_trace(krt_task, "KINFO", rtp);

	ap = (sockaddr_un *) (rtp + 1);

	if (rtp->rtm_addrs & RTA_DST) {
	    dest = ap;
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->rtm_addrs & RTA_GATEWAY) {
	    gate = ap;
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->rtm_addrs & RTA_NETMASK) {
	    sockcopy(ap, &addr);
	    mask = &addr;
	    mask->a.sa_family = dest->a.sa_family;
	    if (ap->a.sa_len) {
		ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	    } else {
		ap = (sockaddr_un *) ((caddr_t) ap + sizeof(u_long));
	    }
	} else {
	    mask = (sockaddr_un *) 0;
	}

	if (rtp->rtm_addrs & RTA_GENMASK) {
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->rtm_addrs & RTA_IFP) {
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->rtm_addrs & RTA_IFA) {
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->rtm_addrs & RTA_AUTHOR) {
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (dest->a.sa_family != AF_INET) {
	    continue;
	}
	install = FALSE;		/* don't install routes in kernel */

	if (rtp->rtm_flags & RTF_HOST) {
	    table = RTS_HOSTROUTE;
	} else {
	    /*
	     *	Route is interior if we have an interface to it or a subnet of it
	     */
	    table = RTS_EXTERIOR;
	    IF_LIST(ifp) {
		if (gd_inet_wholenetof(dest->in.sin_addr) == gd_inet_wholenetof(ifp->int_addr.in.sin_addr)) {
		    table = RTS_INTERIOR;
		    break;
		}
	    } IF_LISTEND(ifp) ;
	}

	/*
	 *	If Kernel route already exists, delete this one, the kernel uses the
	 *	first one
	 */
	if (rt_locate(table, dest, RTPROTO_KERNEL)) {
	    goto Delete;
	}
	/*
	 *	If there was a problem adding the route, delete the kernel route
	 */
	if (!rt_add(dest,
		    mask,
		    gate,
		    (gw_entry *) 0,
		    0,
		    table,
		    RTPROTO_KERNEL,
		    0,
		    (time_t) 0,
		    RTPREF_KERNEL)) {
	    goto Delete;
	}
	continue;

      Delete:
	install = saveinstall;
	krt_delete_dst(krt_task,
		       dest,
		       mask,
		       gate,
		       (flag_t) rtp->rtm_flags);

    }

    (void) free(kbuf);

    install = saveinstall;
}

static void krt_remqueue();

/* Issue a request */
static void
krt_send(tp, rtp)
task *tp;
struct rtm_msg *rtp;
{
    int error = 0;
    const char *sent = "SENT";

    if (!rtp->msghdr.rtm_seq) {
	rtp->msghdr.rtm_seq = ++rtm_head.msghdr.rtm_seq;
	rtp->msghdr.rtm_version = RTM_VERSION;
    }
    rtp->msghdr.rtm_pid = my_pid;

    if (!test_flag && install && tp->task_socket != -1) {
	if (write(tp->task_socket, (caddr_t) &rtp->msghdr, rtp->msghdr.rtm_msglen) < 0) {
	    error = errno;
	    trace(TR_ALL, LOG_ERR, "krt_send: write: %m");
	    sent = "*NOT SENT*";
	}

    }
    if (trace_flags & TR_KRT) {
	krt_trace(tp, sent, &rtp->msghdr);
    }

    switch (error) {
    case EWOULDBLOCK:
    case ENOBUFS:
	/* Indicate request should be retried if the error is not fatal */
	rtp->msghdr.rtm_pid = 0;
	timer_set(tp->task_timer[0], (time_t) KRT_TIMEOUT);
	break;

    default:
	krt_remqueue(tp, rtp);
    }
}


/* Insert at the end of the request queue.  If this is the first element, */
/* call krt_send() to initiate the action. */
static void
krt_addqueue(tp, rtp)
task *tp;
struct rtm_msg *rtp;
{
    /* XXX - Should have logic to consolidate duplicates in the queue */

    /* Insert at the end of the queue */
    insque((struct qelem *) rtp, (struct qelem *) rtm_head.rtm_back);

    if (rtm_head.rtm_forw == rtp) {
	krt_send(tp, rtp);
    }
}


/* Dequeue a successful response.  If there are more on the queue, call */
/* krt_send() to initiate the action */
static void
krt_remqueue(tp, rtp)
task *tp;
struct rtm_msg *rtp;
{
    /* Remove this element from the queue and free it */
    remque((struct qelem *) rtp);
    (void) free((caddr_t) rtp);

    if (rtm_head.rtm_forw != &rtm_head) {
	/* Issue the next request */
	krt_send(tp, rtm_head.rtm_forw);
    } else {
	/* No more requests, reset the timer */
	timer_reset(tp->task_timer[0]);
    }

}


/* Fill in a request and enqueue it */
/* XXX - should allocate large chunks */
static void
krt_request(type, rt)
int type;
rt_entry *rt;
{
    int size;
    struct rtm_msg *rtp;
    struct sockaddr *ap;

    if (!install) {
	return;
    }
    size = sizeof(struct rtm_msg);
    /* Hack to make sure socket lengths are set correctly */
    rt->rt_dest.a.sa_len = sizeof (struct sockaddr_in);
    rt->rt_dest_mask.a.sa_len = sizeof (struct sockaddr_in);
    rt->rt_router.a.sa_len = sizeof (struct sockaddr_in);
    size += socksize(&rt->rt_dest) + socksize(&rt->rt_router);
    size += socksize(&rt->rt_dest_mask) ? socksize(&rt->rt_dest_mask) : sizeof(u_long);

    rtp = (struct rtm_msg *) calloc(1, size);
    if (!rtp) {
	trace(TR_ALL, LOG_ERR, "krt_request: calloc: %m");
	quit(errno);
    }
    rtp->msghdr.rtm_type = type;
    rtp->msghdr.rtm_flags = rt->rt_flags;
    if (rt->rt_ifp->int_state & IFS_UP) {
	rtp->msghdr.rtm_flags |= RTF_UP;
    }
    rtp->msghdr.rtm_msglen = size - (sizeof (struct rtm_msg) - sizeof (struct rt_msghdr));

    /* XXX - set metrics */

    ap = (struct sockaddr *) (rtp + 1);

    sockcopy(&rt->rt_dest, ap);
    ap = (struct sockaddr *) ((caddr_t) ap + ROUNDUP(rt->rt_dest.a.sa_len));
    rtp->msghdr.rtm_addrs |= RTA_DST;

    sockcopy(&rt->rt_router, ap);
    ap = (struct sockaddr *) ((caddr_t) ap + ROUNDUP(rt->rt_router.a.sa_len));
    rtp->msghdr.rtm_addrs |= RTA_GATEWAY;

    if (socksize(&rt->rt_dest_mask)) {
	sockcopy(&rt->rt_dest_mask, ap);
	ap = (struct sockaddr *) ((caddr_t) ap + ROUNDUP(rt->rt_dest_mask.a.sa_len));
    } else {
	memset((caddr_t) ap, 0, sizeof(u_long));
	ap = (struct sockaddr *) ((caddr_t) ap + sizeof(u_long));
    }
    rtp->msghdr.rtm_addrs |= RTA_NETMASK;

    krt_addqueue(krt_task, rtp);
}


/* Process a route socket response from the kernel */
static void
krt_recv(tp)
task *tp;
{
    int size;
    struct rt_msghdr *rtp = (struct rt_msghdr *) recv_iovec[RECV_IOVEC_DATA].iov_base;
    sockaddr_un *ap;
    sockaddr_un *ap1 = (sockaddr_un *) 0;
    sockaddr_un *ap2 = (sockaddr_un *) 0;
    sockaddr_un *ap3 = (sockaddr_un *) 0;
    sockaddr_un *ap4 = (sockaddr_un *) 0;
    rt_entry *rt;

    if (task_receive_packet(tp, &size)) {
	return;
    }
    if (trace_flags & TR_KRT) {
	krt_trace(tp, "RECV", rtp);
    }
    ap = (sockaddr_un *) (rtp + 1);

    if (rtp->rtm_addrs & RTA_DST) {
	ap1 = ap;
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	if (ap1->a.sa_family != AF_INET) {
	    return;
	}
    }
    if (rtp->rtm_addrs & RTA_GATEWAY) {
	ap2 = ap;
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_addrs & RTA_NETMASK) {
	ap3 = ap;
	if (ap->a.sa_len) {
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	} else {
	    ap = (sockaddr_un *) ((caddr_t) ap + sizeof(u_long));
	}
    }
    if (rtp->rtm_addrs & RTA_GENMASK) {
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_addrs & RTA_IFP) {
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_addrs & RTA_IFA) {
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_addrs & RTA_AUTHOR) {
	ap4 = ap;
	ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
    }
    if (rtp->rtm_pid == my_pid) {
	/* XXX - to enable the use of multiple requests outstanding the queue */
	/* XXX - needs to be scanned for this sequence */
	if (rtp->rtm_seq != rtm_head.rtm_forw->msghdr.rtm_seq) {
	    trace(TR_ALL, 0, "krt_recv: invalid message sequence %d, expected %d",
		  rtp->rtm_seq,
		  rtm_head.rtm_forw->msghdr.rtm_seq);
	    return;
	}
	switch (rtp->rtm_type) {
	    case RTM_ADD:
	    case RTM_OLDADD:
	    case RTM_DELETE:
	    case RTM_OLDDEL:
	    case RTM_CHANGE:
	    case RTM_LOCK:
		if (!(rtp->rtm_flags & RTF_DONE)) {
		    errno = rtp->rtm_errno;
		    trace(TR_KRT, LOG_ERR, "krt_recv: %s request for %A/%A via %A: %m",
			  trace_state(rtm_type_bits, rtp->rtm_type - 1),
			  ap1,
			  ap3,
			  ap2);
		} else if (trace_flags & TR_PROTOCOL) {
		    trace(TR_KRT, 0, "krt_recv: sequence %d acknowledged",
			  rtp->rtm_seq);
		}
		krt_remqueue(tp, rtm_head.rtm_forw);
		break;
	    case RTM_GET:
		/* XXX - check for verification of a route change here */
		trace(TR_KRT, 0, "krt_recv: GET response of unknown origin");
		break;
	    default:
		trace(TR_ALL, LOG_ERR, "krt_recv: invalid message type %d",
		      rtp->rtm_type);
		break;
	}
    } else {
	/* Ignore incomplete messages */
	if (!(rtp->rtm_flags & RTF_DONE)) {
	    return;
	}
	switch (rtp->rtm_type) {
	    case RTM_CHANGE:
		if (rt = rt_locate(RTS_HOSTROUTE | RTS_NETROUTE, ap1, RTPROTO_KRT)) {
		    if (!rt_change(rt,
				   ap2,
				   (metric_t) 0,
				   (time_t) 0,	/* XXX - rtm_expire?? */
				   RTPREF_KRT)) {
			trace(TR_ALL, LOG_ERR, "krt_recv: error changing route to %A/%A via %A",
			      ap1,
			      ap3,
			      ap2);
		    }
		} else {
		    /* Route did not exist, add new one */
		    goto add;
		}
		break;
	    case RTM_ADD:
	    case RTM_OLDADD:
	    case RTM_DELETE:
	    case RTM_OLDDEL:
		/* Delete existing route */
		rt_open(tp);
		if (rt = rt_locate(RTS_HOSTROUTE | RTS_NETROUTE, ap1, RTPROTO_KRT)) {
		    (void) rt_delete(rt);
		}
		if (rtp->rtm_type == RTM_DELETE || rtp->rtm_type == RTM_OLDDEL) {
		    rt_close(tp, 0, 1);
		    break;
		}
		/* Add new route */
	      add:
		if (!rt_add(ap1,
			    ap3,
			    ap2,
			    (gw_entry *) 0,
			    (metric_t) 0,
			    RTS_NOAGE | RTS_NOADVISE,
			    RTPROTO_KRT,
			    (as_t) 0,
			    (time_t) 0,	/* XXX - rtm_expire?? */
			    RTPREF_KRT)) {
		    trace(TR_ALL, LOG_ERR, "krt_recv: error adding route to %A/%A via %A",
			  ap1,
			  ap3,
			  ap2);
		}
		rt_close(tp, 0, 1);
		break;
	    case RTM_GET:
		/* ignore */
		break;
	    case RTM_LOSING:
		trace(TR_KRT, 0, "krt_recv: kernel reports TCP lossage on route to %A/%A via %A",
		      ap1,
		      ap3,
		      ap2);
		break;
	    case RTM_REDIRECT:
		trace(TR_KRT, 0, "krt_recv: redirect to %A/%A via %A from %A",
		      ap1,
		      ap3,
		      ap2,
		      ap4);
		/* XXX - rt_redirect() needs to support a netmask */
		rt_redirect(tp, ap1, ap2, ap4, (rtp->rtm_flags & RTF_HOST) ? TRUE : FALSE);
		break;
	    case RTM_MISS:
		trace(TR_KRT, 0, "krt_recv: kernel can not find route to %A/%A via %A",
		      ap1,
		      ap3,
		      ap2);
		break;
	    case RTM_LOCK:
		/* XXX - ignore */
		break;
#ifdef	RTM_RESOLVE
	    case RTM_RESOLVE:
		/* XXX - ignore */
		break;
#endif				/* RTM_RESOLVE */
	}
    }
}


/* Deal with a timeout of a route socket response from the kernel */
/*ARGSUSED*/
static void
krt_timeout(tip, interval)
timer *tip;
time_t interval;
{
    if (rtm_head.rtm_forw->msghdr.rtm_pid) {
	/* No response during timeout period */
	if (rtm_head.rtm_forw->msghdr.rtm_type != RTM_GET) {
	    /* XXX - request timed out, issue a get to determine if it succeded */
	} else {
	    /* Reset pid to indicate a retry */
	    rtm_head.rtm_forw->msghdr.rtm_pid = 0;
	    krt_send(tip->timer_task, rtm_head.rtm_forw);
	}
    } else {
	/* Write failed last time, retry */
	krt_send(tip->timer_task, rtm_head.rtm_forw);
    }
}

#endif				/* RTM_ADD */


int
krt_add(new_rt)
rt_entry *new_rt;
{
    int error = 0;

#ifndef	RTM_ADD
    int do_ioctl = !test_flag && install;
    struct rtentry krt;

#endif				/* RTM_ADD */

    if (new_rt->rt_state & RTS_NOTINSTALL) {
	return (error);
    }
    tracef("KERNEL ADD    %-15A mask %-15A gateway %-15A flags <%s>",
	   &new_rt->rt_dest,
	   &new_rt->rt_dest_mask,
	   &new_rt->rt_router,
	   trace_bits(rt_flag_bits, new_rt->rt_flags));

#ifdef	RTM_ADD
    trace(TR_KRT, 0, NULL);
    krt_request(RTM_ADD, new_rt);
#else				/* RTM_ADD */
    memset((caddr_t) & krt, (char) 0, sizeof(krt));
    krt.rt_dst = new_rt->rt_dest.a;	/* struct copy */
    krt.rt_gateway = new_rt->rt_router.a;	/* struct copy */
    krt.rt_flags = new_rt->rt_flags;
    if (new_rt->rt_ifp->int_state & IFS_UP) {
	krt.rt_flags |= RTF_UP;
    }
    if (do_ioctl && (task_ioctl(krt_task->task_socket, SIOCADDRT, (caddr_t) & krt, sizeof (krt)) < 0)) {
	error = errno;
	trace(TR_ALL | TR_NOSTAMP, LOG_ERR, " SIOCADDRT: %m");
    } else {
	trace(TR_KRT | TR_NOSTAMP, 0, NULL);
    }
#endif				/* RTM_ADD */

    return (error);
}


int
krt_delete(old_rt)
rt_entry *old_rt;
{
    int error = 0;

#ifndef	RTM_ADD
    int do_ioctl = !test_flag && install;
    struct rtentry krt;

#endif				/* RTM_ADD */

    if (old_rt->rt_state & RTS_NOTINSTALL) {
	return (error);
    }
    tracef("KERNEL DELETE %-15A mask %-15A gateway %-15A flags <%s>",
	   &old_rt->rt_dest,
	   &old_rt->rt_dest_mask,
	   &old_rt->rt_router,
	   trace_bits(rt_flag_bits, old_rt->rt_flags));

#ifdef	RTM_ADD
    trace(TR_KRT, 0, NULL);
    krt_request(RTM_DELETE, old_rt);
#else				/* RTM_ADD */
    memset((caddr_t) & krt, (char) 0, sizeof(krt));
    krt.rt_dst = old_rt->rt_dest.a;	/* struct copy */
    krt.rt_gateway = old_rt->rt_router.a;	/* struct copy */
    krt.rt_flags = old_rt->rt_flags;
    if (old_rt->rt_ifp->int_state & IFS_UP) {
	krt.rt_flags |= RTF_UP;
    }
    if (do_ioctl && (task_ioctl(krt_task->task_socket, SIOCDELRT, (caddr_t) & krt, sizeof (krt)) < 0)) {
	error = errno;
	trace(TR_ALL | TR_NOSTAMP, LOG_ERR, " SIOCDELRT: %m");
    } else {
	trace(TR_KRT | TR_NOSTAMP, 0, NULL);
    }
#endif				/* RTM_ADD */

    return (error);
}


int
krt_change(old_rt, new_rt)
rt_entry *old_rt, *new_rt;
{
    int error = 0;

    if (old_rt && new_rt && ((old_rt->rt_state & RTS_NOTINSTALL) == (new_rt->rt_state & RTS_NOTINSTALL))) {
	if ((old_rt->rt_state & RTS_NOTINSTALL) ||
	    equal(&old_rt->rt_router, &new_rt->rt_router) &&
	    (old_rt->rt_flags == new_rt->rt_flags)) {
	    return (error);
#ifdef	RTM_ADD
	} else {
	    trace(TR_KRT, 0, "KERNEL CHANGE %-15A mask %-15A old: gateway %-15A flags <%s> new: gateway %-15A flags <%s>",
		  &old_rt->rt_dest,
		  &old_rt->rt_dest_mask,
		  &old_rt->rt_router,
		  trace_bits(rt_flag_bits, old_rt->rt_flags),
		  &new_rt->rt_router,
		  trace_bits(rt_flag_bits, new_rt->rt_flags));

	    krt_request(RTM_CHANGE, new_rt);
	    return (error);
#endif				/* RTM_ADD */
	}
    }
    if (new_rt && !(new_rt->rt_state & RTS_NOTINSTALL)) {
	error = krt_add(new_rt);
    }
    if (!error && old_rt && !(old_rt->rt_state & RTS_NOTINSTALL)) {
	error = krt_delete(old_rt);
    }
    return (error);
}



#ifdef	RTM_ADD
static void
krt_dump(fp)
FILE *fp;
{
    struct rtm_msg *rtp;
    sockaddr_un *ap;

    (void) fprintf(fp, "Route socket:\n");

    (void) fprintf(fp, "\tSequence:\t%d\n",
		   rtm_head.msghdr.rtm_seq);

    for (rtp = rtm_head.rtm_forw; rtp != &rtm_head; rtp = rtp->rtm_forw) {
	(void) fprintf(fp, "\t\tlength %u  version %u  type %s(%u)  addrs %s(%x)  pid %d  seq %d  error %d",
		       rtp->msghdr.rtm_msglen,
		       rtp->msghdr.rtm_version,
		    trace_state(rtm_type_bits, rtp->msghdr.rtm_type - 1),
		       rtp->msghdr.rtm_type,
		       trace_bits(rtm_sock_bits, rtp->msghdr.rtm_addrs),
		       rtp->msghdr.rtm_addrs,
		       rtp->msghdr.rtm_pid,
		       rtp->msghdr.rtm_seq,
		       rtp->msghdr.rtm_errno);
	if (rtp->msghdr.rtm_errno) {
	    errno = rtp->msghdr.rtm_errno;
	    (void) fprintf(fp, " %m\n");
	} else {
	    (void) fprintf(fp, "\n");
	}

	(void) fprintf(fp, "\t\tflags %s(%x)",
		       trace_bits(rt_flag_bits, rtp->msghdr.rtm_flags),
		       rtp->msghdr.rtm_flags);
	(void) fprintf(fp, "  locks %s(%x)",
		trace_bits(rtm_lock_bits, rtp->msghdr.rtm_rmx.rmx_locks),
		       rtp->msghdr.rtm_rmx.rmx_locks);
	(void) fprintf(fp, "  inits %s(%x)\n",
		       trace_bits(rtm_lock_bits, rtp->msghdr.rtm_inits),
		       rtp->msghdr.rtm_inits);

	/* Display metrics */
	switch (rtp->msghdr.rtm_type) {
	    case RTM_ADD:
	    case RTM_CHANGE:
	    case RTM_GET:
		(void) fprintf(fp, "\t\tmtu %u  hopcount %u  expire %u  ssthresh %u\n",
			       rtp->msghdr.rtm_rmx.rmx_mtu,
			       rtp->msghdr.rtm_rmx.rmx_hopcount,
			       rtp->msghdr.rtm_rmx.rmx_expire,
			       rtp->msghdr.rtm_rmx.rmx_ssthresh);
		(void) fprintf(fp, "\t\trecvpipe %u  sendpipe %u  rtt %u  rttvar %u\n",
			       rtp->msghdr.rtm_rmx.rmx_recvpipe,
			       rtp->msghdr.rtm_rmx.rmx_sendpipe,
			       rtp->msghdr.rtm_rmx.rmx_rtt,
			       rtp->msghdr.rtm_rmx.rmx_rttvar);
		break;
	}
	ap = (sockaddr_un *) (rtp + 1);

	/* Display addresses */
	if (rtp->msghdr.rtm_addrs & RTA_DST) {
	    (void) fprintf(fp, "\t\tdest %A",
			   ap);
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->msghdr.rtm_addrs & RTA_GATEWAY) {
	    (void) fprintf(fp, "  next hop %A",
			   ap);
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->msghdr.rtm_addrs & RTA_NETMASK) {
	    (void) fprintf(fp, "  mask %A",
			   ap);
	    if (ap->a.sa_len) {
		ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	    } else {
		ap = (sockaddr_un *) ((caddr_t) ap + sizeof(u_long));
	    }
	}
	if (rtp->msghdr.rtm_addrs & RTA_GENMASK) {
	    (void) fprintf(fp, "  genmask %A",
			   ap);
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->msghdr.rtm_addrs & RTA_IFP) {
	    (void) fprintf(fp, "  ifp %A",
			   ap);
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->msghdr.rtm_addrs & RTA_IFA) {
	    (void) fprintf(fp, "  ifa %A",
			   ap);
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	if (rtp->msghdr.rtm_addrs & RTA_AUTHOR) {
	    (void) fprintf(fp, "  author %A",
			   ap);
	    ap = (sockaddr_un *) ((caddr_t) ap + ROUNDUP(ap->a.sa_len));
	}
	(void) fprintf(fp, "\n\n");
    }
}

#endif				/* RTM_ADD */


 /*  Initilize the kernel routing table function.  First, create a	*/
 /*  task to hold the socket used in manipulating the kernel routing	*/
 /*  table.  Second, read the initial kernel routing table into		*/
 /*  gated's routing table.						*/
void
krt_init()
{
    int i, kmem;
    int krt_socket;
    struct nlist *nl = (struct nlist *) 0;

#ifndef	AF_ROUTE
#define	AF_ROUTE	AF_INET
#endif				/* AF_ROUTE */
    if ((krt_socket = task_get_socket(AF_ROUTE, SOCK_RAW, 0)) < 0) {
	quit(errno);
    }
    krt_task = task_alloc("KRT");
    krt_task->task_proto = IPPROTO_RAW;
    krt_task->task_socket = krt_socket;
#ifdef	RTM_ADD
    krt_task->task_dump = krt_dump;
    krt_task->task_recv = krt_recv;
#endif				/* RTM_ADD */
    krt_task->task_rtproto = RTPROTO_KERNEL;
    if (!task_create(krt_task, 0)) {
	quit(EINVAL);
    }
#ifdef	RTM_ADD
    (void) timer_create(krt_task,
			0,
			"Timeout",
			TIMERF_ABSOLUTE,
			(time_t) 0,
			krt_timeout);

    if (task_set_option(krt_task,
			TASKOPTION_NONBLOCKING,
			(caddr_t) TRUE) < 0) {
	quit(errno);
    }
    /* Indicate we do not want to see our packets */
    if (task_set_option(krt_task,
			TASKOPTION_USELOOPBACK,
			(caddr_t) FALSE) < 0) {
	quit(errno);
    }
    
#endif				/* RTM_ADD */


    /* Build nlist from symbol names */
    nl = (struct nlist *) calloc(NL_SIZE, sizeof(struct nlist));
    for (i = NL_SIZE; i--;) {
	/* Use memcpy to avoid warning about const char * */
	memcpy((caddr_t) & nl[i].n_un.n_name, (caddr_t) & nl_names[i].nl_name, sizeof(char *));
#ifdef	NLIST_NOUNDER
	if (nl[i].n_name) {
	    nl[i].n_name++;
	}
#endif				/* NLIST_NOUNDER */
	if (nl_names[i].nl_ptr) {
	    *nl_names[i].nl_ptr = &nl[i];
	}
    }

#ifndef vax11c
    if (nlist(UNIX_NAME, nl) < 0) {
	trace(TR_ALL, LOG_ERR, "krt_init: nlist(\"%s\"): %m",
	      UNIX_NAME);
	if (test_flag) {
	    return;
	} else {
	    quit(errno);
	}
    }
    kmem = open("/dev/kmem", O_RDONLY, 0);
    if (kmem < 0) {
	trace(TR_ALL, LOG_ERR, "krt_init: open(\"/dev/kmem\"): %m");
	if (test_flag) {
	    kmem = -1;
	} else {
	    quit(errno);
	}
    }
    if (krt_version->n_value && (kmem >= 0)) {
	char *p;

	if ((version_kernel = (char *) calloc(1, BUFSIZ)) == 0) {
	    trace(TR_ALL, LOG_ERR, "krt_init: calloc: %m");
	    quit(errno);
	}
	(void) lseek(kmem, (off_t) krt_version->n_value, 0);
	(void) read(kmem, version_kernel, BUFSIZ - 1);
	if (p = (char *) index(version_kernel, '\n')) {
	    *p = NULL;
	}
	if ((p = (char *) malloc((unsigned int) strlen(version_kernel)+1)) == 0) {
	    trace(TR_ALL, LOG_ERR, "krt_init: malloc: %m");
	    quit(errno);
	}
	(void) strcpy(p, version_kernel);
	free(version_kernel);
	version_kernel = p;
	trace(TR_INT, 0, NULL);
	trace(TR_INT, 0, "krt_init: %s = %s",
	      krt_version->n_un.n_name,
	      version_kernel);
    } else {
	version_kernel = NULL;
    }
#else				/* vax11c */
    {
	extern char *Network_Image_File;

	(void) multinet_kernel_nlist(Network_Image_File, nl);
    }
    if (krt_multinet_version->n_value && krt_multinet_product_name->n_value && (kmem >= 0)) {
	char *p;

	if ((version_kernel = (char *) calloc(1, BUFSIZ)) == 0) {
	    trace(TR_ALL, LOG_ERR, "krt_init: calloc: %m");
	    quit(errno);
	}
	(void) lseek(kmem, (off_t) krt_multinet_product_name->n_value, 0);
	(void) read(kmem, version_kernel, BUFSIZ - 2);
	(void) strcat(version_kernel, " ");
	(void) lseek(kmem, (off_t) krt_multinet_version->n_value, 0);
	(void) read(kmem, version_kernel + strlen(version_kernel),
		    BUFSIZ - 1 - strlen(version_kernel));
	if ((p = (char *) malloc(strlen(version_kernel)+1)) == 0) {
	    trace(TR_ALL, LOG_ERR, "krt_init: malloc: %m");
	    quit(errno);
	}
	(void) strcpy(p, version_kernel);
	free(version_kernel);
	version_kernel = p;
	trace(TR_INT, 0, "krt_init: %s %s = %s",
	      krt_multinet_product_name->n_name,
	      krt_multinet_version->n_name,
	      version_kernel);
    } else {
	version_kernel = NULL;
    }
#endif				/* vax11c */

    rt_open(krt_task);

    trace(TR_RT, 0, "krt_init: Initial routes read from kernel:");

    krt_rtread(kmem);

    rt_close(krt_task, (gw_entry *) 0, 0);

    if (nl) {
	(void) free((caddr_t) nl);
    }
#ifndef vax11c
    if (kmem >= 0) {
	(void) close(kmem);
    }
#endif				/* vax11c */
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.