/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/* All Rights Reserved	*/

/* Copyright (c) 1990  Mentat Inc. */

/*
 *
 * Copyright (c) 1983, 1989, 1991, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)route.c	8.6 (Berkeley) 4/28/95
 *	@(#)linkaddr.c	8.1 (Berkeley) 6/4/93
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/param.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/stream.h>
#include <sys/sysmacros.h>
#include <sys/tihdr.h>
#include <sys/types.h>
#include <sys/ccompile.h>

#include <net/if.h>
#include <net/route.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <inet/mib2.h>
#include <inet/ip.h>

#include <limits.h>
#include <locale.h>

#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stropts.h>
#include <fcntl.h>
#include <stdarg.h>
#include <assert.h>
#include <strings.h>

#include <libtsnet.h>
#include <tsol/label.h>

static struct keytab {
	char	*kt_cp;
	int	kt_i;
} keywords[] = {
#define	K_ADD		1
	{"add",		K_ADD},
#define	K_BLACKHOLE	2
	{"blackhole",	K_BLACKHOLE},
#define	K_CHANGE	3
	{"change",	K_CHANGE},
#define	K_CLONING	4
	{"cloning",	K_CLONING},
#define	K_DELETE	5
	{"delete",	K_DELETE},
#define	K_DST		6
	{"dst",		K_DST},
#define	K_EXPIRE	7
	{"expire",	K_EXPIRE},
#define	K_FLUSH		8
	{"flush",	K_FLUSH},
#define	K_GATEWAY	9
	{"gateway",	K_GATEWAY},
#define	K_GET		11
	{"get",		K_GET},
#define	K_HOPCOUNT	12
	{"hopcount",	K_HOPCOUNT},
#define	K_HOST		13
	{"host",	K_HOST},
#define	K_IFA		14
	{"ifa",		K_IFA},
#define	K_IFACE		15
	{"iface",	K_IFACE},
#define	K_IFP		16
	{"ifp",		K_IFP},
#define	K_INET		17
	{"inet",	K_INET},
#define	K_INET6		18
	{"inet6",	K_INET6},
#define	K_INTERFACE	19
	{"interface",	K_INTERFACE},
#define	K_LINK		20
	{"link",	K_LINK},
#define	K_LOCK		21
	{"lock",	K_LOCK},
#define	K_LOCKREST	22
	{"lockrest",	K_LOCKREST},
#define	K_MASK		23
	{"mask",	K_MASK},
#define	K_MONITOR	24
	{"monitor",	K_MONITOR},
#define	K_MTU		25
	{"mtu",		K_MTU},
#define	K_NET		26
	{"net",		K_NET},
#define	K_NETMASK	27
	{"netmask",	K_NETMASK},
#define	K_NOSTATIC	28
	{"nostatic",	K_NOSTATIC},
#define	K_PRIVATE	29
	{"private",	K_PRIVATE},
#define	K_PROTO1	30
	{"proto1",	K_PROTO1},
#define	K_PROTO2	31
	{"proto2",	K_PROTO2},
#define	K_RECVPIPE	32
	{"recvpipe",	K_RECVPIPE},
#define	K_REJECT	33
	{"reject",	K_REJECT},
#define	K_RTT		34
	{"rtt",		K_RTT},
#define	K_RTTVAR	35
	{"rttvar",	K_RTTVAR},
#define	K_SA		36
	{"sa",		K_SA},
#define	K_SENDPIPE	37
	{"sendpipe",	K_SENDPIPE},
#define	K_SSTHRESH	38
	{"ssthresh",	K_SSTHRESH},
#define	K_STATIC	39
	{"static",	K_STATIC},
#define	K_XRESOLVE	40
	{"xresolve",	K_XRESOLVE},
#define	K_MULTIRT	41
	{"multirt",	K_MULTIRT},
#define	K_SETSRC	42
	{"setsrc",	K_SETSRC},
#define	K_SHOW		43
	{"show",	K_SHOW},
#define	K_SECATTR	43
	{"secattr",	K_SECATTR},
	{0, 0}
};

/*
 * Size of buffers used to hold command lines from the saved route file as
 * well as error strings.
 */
#define	BUF_SIZE 2048

typedef union sockunion {
	struct	sockaddr sa;
	struct	sockaddr_in sin;
	struct	sockaddr_dl sdl;
	struct	sockaddr_in6 sin6;
} su_t;

/*
 * This structure represents the digested information from parsing arguments
 * to route add, change, delete, and get.
 *
 */
typedef struct rtcmd_irep {
	int ri_cmd;
	int ri_flags;
	int ri_af;
	ulong_t	ri_inits;
	struct rt_metrics ri_metrics;
	int ri_addrs;
	su_t ri_dst;
	char *ri_dest_str;
	su_t ri_src;
	su_t ri_gate;
	struct hostent *ri_gate_hp;
	char *ri_gate_str;
	su_t ri_mask;
	su_t ri_ifa;
	su_t ri_ifp;
	char *ri_ifp_str;
	int ri_rtsa_cnt;	/* number of gateway security attributes */
	struct rtsa_s ri_rtsa;	/* enough space for one attribute */
} rtcmd_irep_t;

typedef struct	mib_item_s {
	struct mib_item_s *next_item;
	long group;
	long mib_id;
	long length;
	intmax_t *valp;
} mib_item_t;

typedef enum {
	ADDR_TYPE_ANY,
	ADDR_TYPE_HOST,
	ADDR_TYPE_NET
} addr_type_t;

typedef enum {
	SEARCH_MODE_NULL,
	SEARCH_MODE_PRINT,
	SEARCH_MODE_DEL
} search_mode_t;

static boolean_t	args_to_rtcmd(rtcmd_irep_t *rcip, char **argv,
    char *cmd_string);
static void		bprintf(FILE *fp, int b, char *s);
static boolean_t	compare_rtcmd(rtcmd_irep_t *srch_rt,
    rtcmd_irep_t *file_rt);
static void		delRouteEntry(mib2_ipRouteEntry_t *rp,
    mib2_ipv6RouteEntry_t *rp6, int seqno);
static void		del_rtcmd_irep(rtcmd_irep_t *rcip);
static void		flushroutes(int argc, char *argv[]);
static boolean_t	getaddr(rtcmd_irep_t *rcip, int which, char *s,
    addr_type_t atype);
static boolean_t	in6_getaddr(char *s, struct sockaddr_in6 *sin6,
    int *plenp, struct hostent **hpp);
static boolean_t	in_getaddr(char *s, struct sockaddr_in *sin,
    int *plenp, int which, struct hostent **hpp, addr_type_t atype,
    rtcmd_irep_t *rcip);
static int		in_getprefixlen(char *addr, int max_plen);
static boolean_t	in_prefixlentomask(int prefixlen, int maxlen,
    uchar_t *mask);
static void		inet_makenetandmask(rtcmd_irep_t *rcip, in_addr_t net,
    struct sockaddr_in *sin);
static in_addr_t	inet_makesubnetmask(in_addr_t addr, in_addr_t mask);
static int		keyword(const char *cp);
static void		link_addr(const char *addr, struct sockaddr_dl *sdl);
static char		*link_ntoa(const struct sockaddr_dl *sdl);
static mib_item_t	*mibget(int sd);
static char		*netname(struct sockaddr *sa);
static int		newroute(char **argv);
static rtcmd_irep_t	*new_rtcmd_irep(void);
static void		pmsg_addrs(const char *cp, size_t len, uint_t addrs);
static void		pmsg_common(const struct rt_msghdr *rtm, size_t len);
static void		print_getmsg(rtcmd_irep_t *req_rt,
    struct rt_msghdr *rtm, int msglen);
static void		print_rtcmd_short(FILE *to, rtcmd_irep_t *rcip,
    boolean_t gw_good, boolean_t to_saved);
static void		print_rtmsg(struct rt_msghdr *rtm, int msglen);
static void		quit(char *s, int err) __NORETURN;
static char		*routename(const struct sockaddr *sa);
static void		rtmonitor(int argc, char *argv[]);
static int		rtmsg(rtcmd_irep_t *rcip);
static int		salen(const struct sockaddr *sa);
static void		save_route(int argc, char **argv, int do_flush);
static void		save_string(char **dst, char *src);
static int		search_rtfile(FILE *fp, FILE *temp_fp, rtcmd_irep_t *rt,
    search_mode_t mode);
static void		set_metric(rtcmd_irep_t *rcip, char *value, int key,
    boolean_t lock);
static int		show_saved_routes(int argc);
static void		sockaddr(char *addr, struct sockaddr *sa);
static void		sodump(su_t *su, char *which);
static void		syntax_arg_missing(char *keyword);
static void		syntax_bad_keyword(char *keyword);
static void		syntax_error(char *err, ...);
static void		usage(char *cp);
static void		write_to_rtfile(FILE *fp, int argc, char **argv);
static void		pmsg_secattr(const char *, size_t, const char *);

static pid_t		pid;
static int		s;
static boolean_t	nflag;
static int		af = AF_INET;
static boolean_t	qflag, tflag;
static boolean_t	verbose;
static boolean_t	debugonly;
static boolean_t	fflag;
static boolean_t	update_table;
static boolean_t	perm_flag;
static boolean_t	early_v6_keyword;
static char		perm_file_sfx[] = "/etc/inet/static_routes";
static char		*perm_file;
static char		temp_file_sfx[] = "/etc/inet/static_routes.tmp";
static char		*temp_file;
static struct in6_addr	in6_host_mask = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
/*
 * WARNING:
 * This next variable indicates whether certain functions exit when an error
 * is detected in the user input.  Currently, exit_on_error is only set false
 * in search_rtfile(), when argument are being parsed.  Only those functions
 * used by search_rtfile() to parse its arguments are designed to work in
 * both modes.  Take particular care in setting this false to ensure that any
 * functions you call that might act on this flag properly return errors when
 * exit_on_error is false.
 */
static int		exit_on_error = B_TRUE;

static struct {
	struct	rt_msghdr m_rtm;
	char	m_space[BUF_SIZE];
} m_rtmsg;

/*
 * Sizes of data structures extracted from the base mib.
 * This allows the size of the tables entries to grow while preserving
 * binary compatibility.
 */
static int ipRouteEntrySize;
static int ipv6RouteEntrySize;

#define	ROUNDUP_LONG(a) \
	((a) > 0 ? (1 + (((a) - 1) | (sizeof (long) - 1))) : sizeof (long))
#define	ADVANCE(x, n) ((x) += ROUNDUP_LONG(salen(n)))
#define	C(x)	((x) & 0xff)

/*
 * return values from in_getprefixlen()
 */
#define	BAD_ADDR	-1	/* prefix is invalid */
#define	NO_PREFIX	-2	/* no prefix was found */

void
usage(char *cp)
{
	if (cp != NULL) {
		(void) fprintf(stderr, gettext("route: botched keyword: %s\n"),
		    cp);
	}
	(void) fprintf(stderr, gettext("usage: route [ -fnpqv ] "
	    "[ -R <root-dir> ] cmd [[ -<qualifers> ] args ]\n"));
	exit(1);
	/* NOTREACHED */
}

/*PRINTFLIKE1*/
void
syntax_error(char *err, ...)
{
	va_list args;

	if (exit_on_error) {
		va_start(args, err);
		(void) vfprintf(stderr, err, args);
		va_end(args);
		exit(1);
	}
	/* NOTREACHED */
}

void
syntax_bad_keyword(char *keyword)
{
	syntax_error(gettext("route: botched keyword: %s\n"), keyword);
}

void
syntax_arg_missing(char *keyword)
{
	syntax_error(gettext("route: argument required following keyword %s\n"),
	    keyword);
}

void
quit(char *s, int sverrno)
{
	(void) fprintf(stderr, "route: ");
	if (s != NULL)
		(void) fprintf(stderr, "%s: ", s);
	(void) fprintf(stderr, "%s\n", strerror(sverrno));
	exit(sverrno);
	/* NOTREACHED */
}

int
main(int argc, char **argv)
{
	extern int optind;
	extern char *optarg;
	int ch;
	int rval;
	size_t size;
	const char *root_dir = NULL;

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	if (argc < 2)
		usage(NULL);

	while ((ch = getopt(argc, argv, "R:nqdtvfp")) != EOF) {
		switch (ch) {
		case 'n':
			nflag = B_TRUE;
			break;
		case 'q':
			qflag = B_TRUE;
			break;
		case 'v':
			verbose = B_TRUE;
			break;
		case 't':
			tflag = B_TRUE;
			break;
		case 'd':
			debugonly = B_TRUE;
			break;
		case 'f':
			fflag = B_TRUE;
			break;
		case 'p':
			perm_flag = B_TRUE;
			break;
		case 'R':
			root_dir = optarg;
			break;
		case '?':
		default:
			usage(NULL);
			/* NOTREACHED */
		}
	}
	argc -= optind;
	argv += optind;

	pid = getpid();
	if (tflag)
		s = open("/dev/null", O_WRONLY);
	else
		s = socket(PF_ROUTE, SOCK_RAW, 0);
	if (s < 0)
		quit("socket", errno);

	/*
	 * Handle the -p and -R flags.  The -R flag only applies
	 * when the -p flag is set.
	 */
	if (root_dir == NULL) {
		perm_file = perm_file_sfx;
		temp_file = temp_file_sfx;
	} else {
		size = strlen(root_dir) + sizeof (perm_file_sfx);
		perm_file = malloc(size);
		if (perm_file == NULL)
			quit("malloc", errno);
		(void) snprintf(perm_file, size, "%s%s", root_dir,
		    perm_file_sfx);
		size = strlen(root_dir) + sizeof (temp_file_sfx);
		temp_file = malloc(size);
		if (temp_file == NULL)
			quit("malloc", errno);
		(void) snprintf(temp_file, size, "%s%s", root_dir,
		    temp_file_sfx);
	}
	/*
	 * Whether or not to act on the routing table.  The only time the
	 * routing table is not modified is when both -p and -R are present.
	 */
	update_table = (!perm_flag || root_dir == NULL);
	if (tflag)
		perm_flag = 0;

	if (fflag) {
		/*
		 * Accept an address family keyword after the -f.  Since the
		 * default address family is AF_INET, reassign af only for the
		 * other valid address families.
		 */
		if (*argv != NULL) {
			switch (keyword(*argv)) {
			case K_INET6:
				af = AF_INET6;
				early_v6_keyword = B_TRUE;
				/* fallthrough */
			case K_INET:
				/* Skip over the address family parameter. */
				argc--;
				argv++;
				break;
			}
		}
		flushroutes(0, NULL);
	}

	if (*argv != NULL) {
		switch (keyword(*argv)) {
		case K_GET:
		case K_CHANGE:
		case K_ADD:
		case K_DELETE:
			rval = 0;
			if (update_table) {
				rval = newroute(argv);
			}
			if (perm_flag && (rval == 0 || rval == EEXIST ||
			    rval == ESRCH)) {
				save_route(argc, argv, B_FALSE);
				return (0);
			}
			return (rval);
		case K_SHOW:
			if (perm_flag) {
				return (show_saved_routes(argc));
			} else {
				syntax_error(gettext(
				    "route: show command requires -p\n"));
			}
			/* NOTREACHED */
		case K_MONITOR:
			rtmonitor(argc, argv);
			/* NOTREACHED */

		case K_FLUSH:
			flushroutes(argc, argv);
			return (0);
		}
	}
	if (!fflag)
		usage(*argv);
	return (0);
}

/*
 * Purge all entries in the routing tables not
 * associated with network interfaces.
 */
void
flushroutes(int argc, char *argv[])
{
	int seqno;
	int sd;	/* mib stream */
	mib_item_t	*item;
	mib2_ipRouteEntry_t *rp;
	mib2_ipv6RouteEntry_t *rp6;
	int oerrno;
	int off = 0;
	int on = 1;

	if (argc > 1) {
		argv++;
		if (argc == 2 && **argv == '-') {
			/*
			 * The address family (preceded by a dash) may be used
			 * to flush the routes of that particular family.
			 */
			switch (keyword(*argv + 1)) {
			case K_INET:
				af = AF_INET;
				break;
			case K_LINK:
				af = AF_LINK;
				break;
			case K_INET6:
				af = AF_INET6;
				break;
			default:
				usage(*argv);
				/* NOTREACHED */
			}
		} else {
			usage(*argv);
		}
	}
	if (perm_flag) {
		/* This flushes the persistent route file */
		save_route(0, NULL, B_TRUE);
	}
	if (!update_table) {
		return;
	}

	if (setsockopt(s, SOL_SOCKET, SO_USELOOPBACK, (char *)&off,
	    sizeof (off)) < 0)
		quit("setsockopt", errno);

	sd = open("/dev/ip", O_RDWR);
	oerrno = errno;
	if (sd < 0) {
		switch (errno) {
		case EACCES:
			(void) fprintf(stderr,
			    gettext("route: flush: insufficient privileges\n"));
			exit(oerrno);
			/* NOTREACHED */
		default:
			quit(gettext("can't open mib stream"), oerrno);
			/* NOTREACHED */
		}
	}
	if ((item = mibget(sd)) == NULL)
		quit("mibget", errno);
	if (verbose) {
		(void) printf("Examining routing table from "
		    "T_SVR4_OPTMGMT_REQ\n");
	}
	seqno = 0;		/* ??? */
	switch (af) {
	case AF_INET:
		/* Extract ipRouteEntrySize */
		for (; item != NULL; item = item->next_item) {
			if (item->mib_id != 0)
				continue;
			if (item->group == MIB2_IP) {
				ipRouteEntrySize =
				    ((mib2_ip_t *)item->valp)->ipRouteEntrySize;
				assert(IS_P2ALIGNED(ipRouteEntrySize,
				    sizeof (mib2_ipRouteEntry_t *)));
				break;
			}
		}
		if (ipRouteEntrySize == 0) {
			(void) fprintf(stderr,
			    gettext("ipRouteEntrySize can't be determined.\n"));
			exit(1);
		}
		for (; item != NULL; item = item->next_item) {
			/*
			 * skip all the other trash that comes up the mib stream
			 */
			if (item->group != MIB2_IP ||
			    item->mib_id != MIB2_IP_ROUTE)
				continue;
			for (rp = (mib2_ipRouteEntry_t *)item->valp;
			    (char *)rp < (char *)item->valp + item->length;
			    /* LINTED */
			    rp = (mib2_ipRouteEntry_t *)
				((char *)rp + ipRouteEntrySize)) {
				delRouteEntry(rp, NULL, seqno);
				seqno++;
			}
			break;
		}
		break;
	case AF_INET6:
		/* Extract ipv6RouteEntrySize */
		for (; item != NULL; item = item->next_item) {
			if (item->mib_id != 0)
				continue;
			if (item->group == MIB2_IP6) {
				ipv6RouteEntrySize =
				    ((mib2_ipv6IfStatsEntry_t *)item->valp)->
					ipv6RouteEntrySize;
				assert(IS_P2ALIGNED(ipv6RouteEntrySize,
				    sizeof (mib2_ipv6RouteEntry_t *)));
				break;
			}
		}
		if (ipv6RouteEntrySize == 0) {
			(void) fprintf(stderr, gettext(
			    "ipv6RouteEntrySize cannot be determined.\n"));
			exit(1);
		}
		for (; item != NULL; item = item->next_item) {
			/*
			 * skip all the other trash that comes up the mib stream
			 */
			if (item->group != MIB2_IP6 ||
			    item->mib_id != MIB2_IP6_ROUTE)
				continue;
			for (rp6 = (mib2_ipv6RouteEntry_t *)item->valp;
			    (char *)rp6 < (char *)item->valp + item->length;
			    /* LINTED */
			    rp6 = (mib2_ipv6RouteEntry_t *)
				((char *)rp6 + ipv6RouteEntrySize)) {
				delRouteEntry(NULL, rp6, seqno);
				seqno++;
			}
			break;
		}
		break;
	}

	if (setsockopt(s, SOL_SOCKET, SO_USELOOPBACK, (char *)&on,
	    sizeof (on)) < 0)
		quit("setsockopt", errno);
}

/*
 * Given the contents of a mib_item_t of id type MIB2_IP_ROUTE or
 * MIB2_IP6_ROUTE, construct and send an RTM_DELETE routing socket message in
 * order to facilitate the flushing of RTF_GATEWAY routes.
 */
static void
delRouteEntry(mib2_ipRouteEntry_t *rp, mib2_ipv6RouteEntry_t *rp6, int seqno)
{
	char *cp;
	int ire_type;
	int rlen;
	struct rt_msghdr *rtm;
	struct sockaddr_in sin;
	struct sockaddr_in6 sin6;
	int slen;

	if (rp != NULL)
		ire_type = rp->ipRouteInfo.re_ire_type;
	else
		ire_type = rp6->ipv6RouteInfo.re_ire_type;
	if (ire_type != IRE_DEFAULT &&
	    ire_type != IRE_PREFIX &&
	    ire_type != IRE_HOST &&
	    ire_type != IRE_HOST_REDIRECT)
		return;

	rtm = &m_rtmsg.m_rtm;
	(void) memset(rtm, 0, sizeof (m_rtmsg));
	rtm->rtm_type = RTM_DELETE;
	rtm->rtm_seq = seqno;
	rtm->rtm_flags |= RTF_GATEWAY;
	rtm->rtm_version = RTM_VERSION;
	rtm->rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
	cp = m_rtmsg.m_space;
	if (rp != NULL) {
		slen = sizeof (struct sockaddr_in);
		if (rp->ipRouteMask == IP_HOST_MASK)
			rtm->rtm_flags |= RTF_HOST;
		(void) memset(&sin, 0, slen);
		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = rp->ipRouteDest;
		(void) memmove(cp, &sin, slen);
		cp += slen;
		sin.sin_addr.s_addr = rp->ipRouteNextHop;
		(void) memmove(cp, &sin, slen);
		cp += slen;
		sin.sin_addr.s_addr = rp->ipRouteMask;
		(void) memmove(cp, &sin, slen);
		cp += slen;
	} else {
		slen = sizeof (struct sockaddr_in6);
		if (rp6->ipv6RoutePfxLength == IPV6_ABITS)
			rtm->rtm_flags |= RTF_HOST;
		(void) memset(&sin6, 0, slen);
		sin6.sin6_family = AF_INET6;
		sin6.sin6_addr = rp6->ipv6RouteDest;
		(void) memmove(cp, &sin6, slen);
		cp += slen;
		sin6.sin6_addr = rp6->ipv6RouteNextHop;
		(void) memmove(cp, &sin6, slen);
		cp += slen;
		(void) memset(&sin6.sin6_addr, 0, sizeof (sin6.sin6_addr));
		(void) in_prefixlentomask(rp6->ipv6RoutePfxLength, IPV6_ABITS,
		    (uchar_t *)&sin6.sin6_addr.s6_addr);
		(void) memmove(cp, &sin6, slen);
		cp += slen;
	}
	rtm->rtm_msglen = cp - (char *)&m_rtmsg;
	if (debugonly) {
		/*
		 * In debugonly mode, the routing socket message to delete the
		 * current entry is not actually sent.  However if verbose is
		 * also set, the routing socket message that would have been
		 * is printed.
		 */
		if (verbose)
			print_rtmsg(rtm, rtm->rtm_msglen);
		return;
	}

	rlen = write(s, (char *)&m_rtmsg, rtm->rtm_msglen);
	if (rlen < (int)rtm->rtm_msglen) {
		if (rlen < 0) {
			(void) fprintf(stderr,
			    gettext("route: write to routing socket: %s\n"),
			    strerror(errno));
		} else {
			(void) fprintf(stderr, gettext("route: write to "
			    "routing socket got only %d for rlen\n"), rlen);
		}
		return;
	}
	if (qflag) {
		/*
		 * In quiet mode, nothing is printed at all (unless the write()
		 * itself failed.
		 */
		return;
	}
	if (verbose) {
		print_rtmsg(rtm, rlen);
	} else {
		struct sockaddr *sa = (struct sockaddr *)(rtm + 1);

		(void) printf("%-20.20s ",
		    rtm->rtm_flags & RTF_HOST ? routename(sa) :
			netname(sa));
		/* LINTED */
		sa = (struct sockaddr *)(salen(sa) + (char *)sa);
		(void) printf("%-20.20s ", routename(sa));
		(void) printf("done\n");
	}
}

/*
 * Return the name of the host whose address is given.
 */
char *
routename(const struct sockaddr *sa)
{
	char *cp;
	static char line[MAXHOSTNAMELEN + 1];
	struct hostent *hp = NULL;
	static char domain[MAXHOSTNAMELEN + 1];
	static boolean_t first = B_TRUE;
	struct in_addr in;
	struct in6_addr in6;
	int error_num;
	ushort_t *s;
	ushort_t *slim;

	if (first) {
		first = B_FALSE;
		if (gethostname(domain, MAXHOSTNAMELEN) == 0 &&
		    (cp = strchr(domain, '.')))
			(void) strcpy(domain, cp + 1);
		else
			domain[0] = 0;
	}

	if (salen(sa) == 0) {
		(void) strcpy(line, "default");
		return (line);
	}
	switch (sa->sa_family) {

	case AF_INET:
		/* LINTED */
		in = ((struct sockaddr_in *)sa)->sin_addr;

		cp = NULL;
		if (in.s_addr == INADDR_ANY)
			cp = "default";
		if (cp == NULL && !nflag) {
			hp = gethostbyaddr((char *)&in, sizeof (struct in_addr),
				AF_INET);
			if (hp != NULL) {
				if (((cp = strchr(hp->h_name, '.')) != NULL) &&
				    (strcmp(cp + 1, domain) == 0))
					*cp = 0;
				cp = hp->h_name;
			}
		}
		if (cp != NULL) {
			(void) strncpy(line, cp, MAXHOSTNAMELEN);
			line[MAXHOSTNAMELEN] = '\0';
		} else {
			in.s_addr = ntohl(in.s_addr);
			(void) sprintf(line, "%u.%u.%u.%u", C(in.s_addr >> 24),
			    C(in.s_addr >> 16), C(in.s_addr >> 8),
			    C(in.s_addr));
		}
		break;

	case AF_LINK:
		return (link_ntoa((struct sockaddr_dl *)sa));

	case AF_INET6:
		/* LINTED */
		in6 = ((struct sockaddr_in6 *)sa)->sin6_addr;

		cp = NULL;
		if (IN6_IS_ADDR_UNSPECIFIED(&in6))
			cp = "default";
		if (cp == NULL && !nflag) {
			hp = getipnodebyaddr((char *)&in6,
				sizeof (struct in6_addr), AF_INET6, &error_num);
			if (hp != NULL) {
				if (((cp = strchr(hp->h_name, '.')) != NULL) &&
				    (strcmp(cp + 1, domain) == 0))
					*cp = 0;
				cp = hp->h_name;
			}
		}
		if (cp != NULL) {
			(void) strncpy(line, cp, MAXHOSTNAMELEN);
			line[MAXHOSTNAMELEN] = '\0';
		} else {
			(void) inet_ntop(AF_INET6, (void *)&in6, line,
			    INET6_ADDRSTRLEN);
		}
		if (hp != NULL)
			freehostent(hp);

		break;

	default:
		s = (ushort_t *)sa;

		slim = s + ((salen(sa) + 1) >> 1);
		cp = line + sprintf(line, "(%d)", sa->sa_family);

		while (++s < slim) /* start with sa->sa_data */
			cp += sprintf(cp, " %x", *s);
		break;
	}
	return (line);
}

/*
 * Return the name of the network whose address is given.
 * The address is assumed to be that of a net or subnet, not a host.
 */
static char *
netname(struct sockaddr *sa)
{
	char *cp = NULL;
	static char line[MAXHOSTNAMELEN + 1];
	struct netent *np;
	in_addr_t net, mask;
	int subnetshift;
	struct in_addr in;
	ushort_t *s;
	ushort_t *slim;

	switch (sa->sa_family) {

	case AF_INET:
		/* LINTED */
		in = ((struct sockaddr_in *)sa)->sin_addr;

		in.s_addr = ntohl(in.s_addr);
		if (in.s_addr == INADDR_ANY) {
			cp = "default";
		} else if (!nflag) {
			if (IN_CLASSA(in.s_addr)) {
				mask = IN_CLASSA_NET;
				subnetshift = 8;
			} else if (IN_CLASSB(in.s_addr)) {
				mask = IN_CLASSB_NET;
				subnetshift = 8;
			} else {
				mask = IN_CLASSC_NET;
				subnetshift = 4;
			}
			/*
			 * If there are more bits than the standard mask
			 * would suggest, subnets must be in use.
			 * Guess at the subnet mask, assuming reasonable
			 * width subnet fields.
			 */
			while (in.s_addr &~ mask)
				mask = (long)mask >> subnetshift;
			net = in.s_addr & mask;
			while ((mask & 1) == 0)
				mask >>= 1, net >>= 1;
			np = getnetbyaddr(net, AF_INET);
			if (np != NULL)
				cp = np->n_name;
		}
		if (cp != NULL) {
			(void) strncpy(line, cp, MAXHOSTNAMELEN);
			line[MAXHOSTNAMELEN] = '\0';
		} else if ((in.s_addr & 0xffffff) == 0) {
			(void) sprintf(line, "%u", C(in.s_addr >> 24));
		} else if ((in.s_addr & 0xffff) == 0) {
			(void) sprintf(line, "%u.%u", C(in.s_addr >> 24),
			    C(in.s_addr >> 16));
		} else if ((in.s_addr & 0xff) == 0) {
			(void) sprintf(line, "%u.%u.%u", C(in.s_addr >> 24),
			    C(in.s_addr >> 16), C(in.s_addr >> 8));
		} else {
			(void) sprintf(line, "%u.%u.%u.%u", C(in.s_addr >> 24),
			    C(in.s_addr >> 16), C(in.s_addr >> 8),
			    C(in.s_addr));
		}
		break;

	case AF_LINK:
		return (link_ntoa((struct sockaddr_dl *)sa));

	case AF_INET6:
		return (routename(sa));

	default:
		/* LINTED */
		s = (ushort_t *)sa->sa_data;

		slim = s + ((salen(sa) + 1) >> 1);
		cp = line + sprintf(line, "af %d:", sa->sa_family);

		while (s < slim)
			cp += sprintf(cp, " %x", *s++);
		break;
	}
	return (line);
}

/*
 * Initialize a new structure.  Keep in mind that ri_dst_str, ri_gate_str and
 * ri_ifp_str will be freed by det_rtcmd_irep, so they should either be NULL
 * or point to dynamically allocated memory.
 */
rtcmd_irep_t *
new_rtcmd_irep(void)
{
	rtcmd_irep_t *rcip;

	rcip = calloc(1, sizeof (rtcmd_irep_t));
	if (rcip == NULL) {
		quit("calloc", errno);
	}
	rcip->ri_af = af;
	rcip->ri_flags = RTF_STATIC;
	return (rcip);
}

void
del_rtcmd_irep(rtcmd_irep_t *rcip)
{
	free(rcip->ri_dest_str);
	free(rcip->ri_gate_str);
	free(rcip->ri_ifp_str);
	/*
	 * IPv6 host entries come from getipnodebyname, which dynamically
	 * allocates memory.  IPv4 host entries come from gethostbyname, which
	 * returns static memory and cannot be freed with freehostent.
	 */
	if (rcip->ri_gate_hp != NULL &&
	    rcip->ri_gate_hp->h_addrtype == AF_INET6)
		freehostent(rcip->ri_gate_hp);
	free(rcip);
}

void
save_string(char **dst, char *src)
{
	free(*dst);
	*dst = strdup(src);
	if (*dst == NULL) {
		quit("malloc", errno);
	}
}

/*
 * Print the short form summary of a route command.
 * Eg. "add net default: gateway 10.0.0.1"
 * The final newline is not added, allowing the caller to append additional
 * information.
 */
void
print_rtcmd_short(FILE *to, rtcmd_irep_t *rcip, boolean_t gw_good,
    boolean_t to_saved)
{
	char *cmd;
	char obuf[INET6_ADDRSTRLEN];

	switch (rcip->ri_cmd) {
	case RTM_ADD:
		cmd = "add";
		break;
	case RTM_CHANGE:
		cmd = "change";
		break;
	case RTM_DELETE:
		cmd = "delete";
		break;
	case RTM_GET:
		cmd = "get";
		break;
	default:
		assert(0);
	}

	(void) fprintf(to, "%s%s %s %s", cmd,
	    (to_saved) ? " persistent" : "",
	    (rcip->ri_flags & RTF_HOST) ? "host" : "net",
	    (rcip->ri_dest_str == NULL) ? "NULL" : rcip->ri_dest_str);

	if (rcip->ri_gate_str != NULL) {
		switch (rcip->ri_af) {
		case AF_INET:
			if (nflag) {
				(void) fprintf(to, ": gateway %s",
				    inet_ntoa(rcip->ri_gate.sin.sin_addr));
			} else if (gw_good &&
			    rcip->ri_gate_hp != NULL &&
			    rcip->ri_gate_hp->h_addr_list[1] != NULL) {
				/*
				 * Print the actual address used in the case
				 * where there was more than one address
				 * available for the name, and one was used
				 * successfully.
				 */
				(void) fprintf(to, ": gateway %s (%s)",
				    rcip->ri_gate_str,
				    inet_ntoa(rcip->ri_gate.sin.sin_addr));
			} else {
				(void) fprintf(to, ": gateway %s",
				    rcip->ri_gate_str);
			}
			break;
		case AF_INET6:
			if (inet_ntop(AF_INET6,
				&rcip->ri_gate.sin6.sin6_addr, obuf,
				INET6_ADDRSTRLEN) != NULL) {
				if (nflag) {
					(void) fprintf(to, ": gateway %s",
					    obuf);
					break;
				}
				if (gw_good &&
				    rcip->ri_gate_hp->h_addr_list[1] != NULL) {
					(void) fprintf(to, ": gateway %s (%s)",
					    rcip->ri_gate_str, obuf);
					break;
				}
			}
			/* FALLTHROUGH */
		default:
			(void) fprintf(to, ": gateway %s",
			    rcip->ri_gate_str);
			break;
		}
	}
}

void
set_metric(rtcmd_irep_t *rcip, char *value, int key, boolean_t lock)
{
	int flag = 0;
	uint_t noval, *valp = &noval;

	switch (key) {
#define	caseof(x, y, z)	\
	case (x): valp = &(rcip->ri_metrics.z); flag = (y); break

	caseof(K_MTU, RTV_MTU, rmx_mtu);
	caseof(K_HOPCOUNT, RTV_HOPCOUNT, rmx_hopcount);
	caseof(K_EXPIRE, RTV_EXPIRE, rmx_expire);
	caseof(K_RECVPIPE, RTV_RPIPE, rmx_recvpipe);
	caseof(K_SENDPIPE, RTV_SPIPE, rmx_sendpipe);
	caseof(K_SSTHRESH, RTV_SSTHRESH, rmx_ssthresh);
	caseof(K_RTT, RTV_RTT, rmx_rtt);
	caseof(K_RTTVAR, RTV_RTTVAR, rmx_rttvar);
#undef	caseof
	}
	rcip->ri_inits |= flag;
	if (lock)
		rcip->ri_metrics.rmx_locks |= flag;
	*valp = atoi(value);
}

/*
 * Parse the options give in argv[], filling in rcip with the results.
 * If cmd_string is non-null, argc and argv are ignored, and cmd_string is
 * tokenized to produce the command line.  Cmd_string is tokenized using
 * strtok, which will overwrite whitespace in the string with nulls.
 *
 * Returns B_TRUE on success and B_FALSE on failure.
 */
boolean_t
args_to_rtcmd(rtcmd_irep_t *rcip, char **argv, char *cmd_string)
{
	const char *ws = "\f\n\r\t\v ";
	char *tok = cmd_string;
	char *keyword_str;
	addr_type_t atype = ADDR_TYPE_ANY;
	boolean_t iflag = B_FALSE;
	boolean_t locknext = B_FALSE;
	boolean_t lockrest = B_FALSE;
	boolean_t dash_keyword;
	int key;
	char *err;

	if (cmd_string == NULL) {
		tok = argv[0];
	} else {
		tok = strtok(cmd_string, ws);
	}

	/*
	 * The command keywords are already fully checked by main() or
	 * search_rtfile().
	 */
	switch (*tok) {
	case 'a':
		rcip->ri_cmd = RTM_ADD;
		break;
	case 'c':
		rcip->ri_cmd = RTM_CHANGE;
		break;
	case 'd':
		rcip->ri_cmd = RTM_DELETE;
		break;
	case 'g':
		rcip->ri_cmd = RTM_GET;
		break;
	default:
		/* NOTREACHED */
		quit(gettext("Internal Error"), EINVAL);
		/* NOTREACHED */
	}

#define	NEXTTOKEN \
	((tok = (cmd_string == NULL ? *++argv : strtok(NULL, ws))) != NULL)

	while (NEXTTOKEN) {
		keyword_str = tok;
		if (*tok == '-') {
			dash_keyword = B_TRUE;
			key = keyword(tok + 1);
		} else {
			dash_keyword = B_FALSE;
			key = keyword(tok);
			if (key != K_HOST && key != K_NET) {
				/* All others must be preceded by '-' */
				key = 0;
			}
		}
		switch (key) {
		case K_HOST:
			if (atype == ADDR_TYPE_NET) {
				syntax_error(gettext("route: -host and -net "
				    "are mutually exclusive\n"));
				return (B_FALSE);
			}
			atype = ADDR_TYPE_HOST;
			break;
		case K_NET:
			if (atype == ADDR_TYPE_HOST) {
				syntax_error(gettext("route: -host and -net "
				    "are mutually exclusive\n"));
				return (B_FALSE);
			}
			atype = ADDR_TYPE_NET;
			break;
		case K_LINK:
			rcip->ri_af = AF_LINK;
			break;
		case K_INET:
			rcip->ri_af = AF_INET;
			break;
		case K_SA:
			rcip->ri_af = PF_ROUTE;
			break;
		case K_INET6:
			rcip->ri_af = AF_INET6;
			break;
		case K_IFACE:
		case K_INTERFACE:
			iflag = B_TRUE;
			/* fallthrough */
		case K_NOSTATIC:
			rcip->ri_flags &= ~RTF_STATIC;
			break;
		case K_LOCK:
			locknext = B_TRUE;
			break;
		case K_LOCKREST:
			lockrest = B_TRUE;
			break;
		case K_REJECT:
			rcip->ri_flags |= RTF_REJECT;
			break;
		case K_BLACKHOLE:
			rcip->ri_flags |= RTF_BLACKHOLE;
			break;
		case K_PROTO1:
			rcip->ri_flags |= RTF_PROTO1;
			break;
		case K_PROTO2:
			rcip->ri_flags |= RTF_PROTO2;
			break;
		case K_CLONING:
			rcip->ri_flags |= RTF_CLONING;
			break;
		case K_XRESOLVE:
			rcip->ri_flags |= RTF_XRESOLVE;
			break;
		case K_STATIC:
			rcip->ri_flags |= RTF_STATIC;
			break;
		case K_IFA:
			if (!NEXTTOKEN) {
				syntax_arg_missing(keyword_str);
				return (B_FALSE);
			}
			if (!getaddr(rcip, RTA_IFA, tok, atype)) {
				return (B_FALSE);
			}
			break;
		case K_IFP:
			if (!NEXTTOKEN) {
				syntax_arg_missing(keyword_str);
				return (B_FALSE);
			}
			if (!getaddr(rcip, RTA_IFP, tok, atype)) {
				return (B_FALSE);
			}
			break;
		case K_GATEWAY:
			if (!NEXTTOKEN) {
				syntax_arg_missing(keyword_str);
				return (B_FALSE);
			}
			if (!getaddr(rcip, RTA_GATEWAY, tok, atype)) {
				return (B_FALSE);
			}
			break;
		case K_DST:
			if (!NEXTTOKEN) {
				syntax_arg_missing(keyword_str);
				return (B_FALSE);
			}
			if (!getaddr(rcip, RTA_DST, tok, atype)) {
				return (B_FALSE);
			}
			break;
		case K_NETMASK:
			if (!NEXTTOKEN) {
				syntax_arg_missing(keyword_str);
				return (B_FALSE);
			}
			if (!getaddr(rcip, RTA_NETMASK, tok, atype)) {
				return (B_FALSE);
			}
			atype = ADDR_TYPE_NET;
			break;
		case K_MTU:
		case K_HOPCOUNT:
		case K_EXPIRE:
		case K_RECVPIPE:
		case K_SENDPIPE:
		case K_SSTHRESH:
		case K_RTT:
		case K_RTTVAR:
			if (!NEXTTOKEN) {
				syntax_arg_missing(keyword_str);
				return (B_FALSE);
			}
			set_metric(rcip, tok, key, locknext || lockrest);
			locknext = B_FALSE;
			break;
		case K_PRIVATE:
			rcip->ri_flags |= RTF_PRIVATE;
			break;
		case K_MULTIRT:
			rcip->ri_flags |= RTF_MULTIRT;
			break;
		case K_SETSRC:
			if (!NEXTTOKEN) {
				syntax_arg_missing(keyword_str);
				return (B_FALSE);
			}
			if (!getaddr(rcip, RTA_SRC, tok, atype)) {
				return (B_FALSE);
			}
			rcip->ri_flags |= RTF_SETSRC;
			break;
		case K_SECATTR:
			if (!NEXTTOKEN) {
				syntax_arg_missing(keyword_str);
				return (B_FALSE);
			}
			if (is_system_labeled()) {
				int err;

				if (rcip->ri_rtsa_cnt >= 1) {
					syntax_error(gettext("route: can't "
					    "specify more than one security "
					    "attribute\n"));
					return (B_FALSE);
				}
				if (!rtsa_keyword(tok, &rcip->ri_rtsa, &err,
				    NULL)) {
					syntax_error(gettext("route: "
					    "bad security attribute: %s\n"),
					    tsol_strerror(err, errno));
					return (B_FALSE);
				}
				rcip->ri_rtsa_cnt++;
			} else {
				syntax_error(gettext("route: "
				    "system is not labeled; cannot specify "
				    "security attributes.\n"));
				return (B_FALSE);
			}
			break;
		default:
			if (dash_keyword) {
				syntax_bad_keyword(tok + 1);
				return (B_FALSE);
			}
			if ((rcip->ri_addrs & RTA_DST) == 0) {
				if (!getaddr(rcip, RTA_DST, tok, atype)) {
					return (B_FALSE);
				}
			} else if ((rcip->ri_addrs & RTA_GATEWAY) == 0) {
				/*
				 * For the gateway parameter, retrieve the
				 * pointer to the struct hostent so that all
				 * possible addresses can be tried until one
				 * is successful.
				 */
				if (!getaddr(rcip, RTA_GATEWAY, tok, atype)) {
					return (B_FALSE);
				}
			} else {
				ulong_t metric;
				/*
				 * Assume that a regular number is a metric.
				 * Needed for compatibility with old route
				 * command syntax.
				 */
				errno = 0;
				metric = strtoul(tok, &err, 10);
				if (errno == 0 && *err == '\0' &&
				    metric < 0x80000000ul) {
					iflag = (metric == 0);
					if (verbose) {
						(void) printf("old usage of "
						    "trailing number, assuming "
						    "route %s\n", iflag ?
						    "to if" : "via gateway");
					}
					continue;
				}
				if (!getaddr(rcip, RTA_NETMASK, tok, atype)) {
					return (B_FALSE);
				}
			}
		}
	}
#undef NEXTTOKEN

	if ((rcip->ri_addrs & RTA_DST) == 0) {
		syntax_error(gettext("route: destination required\n"));
		return (B_FALSE);
	} else if ((rcip->ri_cmd == RTM_ADD || rcip->ri_cmd == RTM_DELETE) &&
	    (rcip->ri_addrs & RTA_GATEWAY) == 0) {
		syntax_error(gettext(
		    "route: gateway required for add or delete command\n"));
		return (B_FALSE);
	}

	if (!iflag) {
		rcip->ri_flags |= RTF_GATEWAY;
	}

	if (atype != ADDR_TYPE_NET) {
		if (rcip->ri_addrs & RTA_NETMASK) {
			/*
			 * We know the netmask, so we can set the host flag
			 * based on whether the netmask is the host netmask.
			 */
			if (rcip->ri_af == AF_INET &&
			    rcip->ri_mask.sin.sin_addr.s_addr ==
			    IP_HOST_MASK) {
				rcip->ri_flags |= RTF_HOST;
			}
			if (rcip->ri_af == AF_INET6 &&
			    memcmp(&rcip->ri_mask.sin6.sin6_addr,
				&in6_host_mask,
				sizeof (struct in6_addr)) == 0) {
				rcip->ri_flags |= RTF_HOST;
			}
		} else {
			/*
			 * If no prefix mask has been saved at this point, it
			 * only makes sense to treat the destination address
			 * as a host address.
			 */
			rcip->ri_flags |= RTF_HOST;
		}
	}
	return (B_TRUE);
}

/*
 * This command always seeks to the end of the file prior to writing.
 */
void
write_to_rtfile(FILE *fp, int argc, char **argv)
{
	char file_line[BUF_SIZE];
	int len;
	int i;

	len = 0;
	if (early_v6_keyword) {
		/*
		 * This flag is set when "inet6" was seen as an
		 * argument to the -f flag.  Normally, when writing
		 * routes to the persistent route file, everything on
		 * the command line after "add" is saved verbatim.
		 * In this case, the arguments after "add" may not be
		 * sufficient, as the ipv6 keyword came before "add",
		 * yet must be present in the persistent route file.
		 */
		len += snprintf(file_line, BUF_SIZE, "-inet6 ");
	}
	for (i = 0; argc > 0 && len < BUF_SIZE; i++, argc--) {
		len += snprintf(&file_line[len], BUF_SIZE - len, "%s ",
		    argv[i]);
	}
	if (len >= BUF_SIZE)
		quit(gettext("Internal Error"), EINVAL);
	file_line[len - 1] = '\n';
	if (fseek(fp, 0, SEEK_END) != 0 ||
	    fputs(file_line, fp) == EOF) {
		quit(gettext("failed to write to route file"),
		    errno);
	}
}

boolean_t
compare_rtcmd(rtcmd_irep_t *srch_rt, rtcmd_irep_t *file_rt)
{
	if (strcmp(srch_rt->ri_dest_str, file_rt->ri_dest_str) != 0 ||
	    memcmp(&srch_rt->ri_mask, &file_rt->ri_mask, sizeof (su_t)) != 0) {
		return (B_FALSE);
	}
	return (srch_rt->ri_gate_str == NULL ||
	    strcmp(srch_rt->ri_gate_str, file_rt->ri_gate_str) == 0);
}

/*
 * Search the route file for routes matching the supplied route.  There are 3
 * modes of operation:
 *    SEARCH_MODE_RET - no side effects.
 *    SEARCH_MODE_PRINT - prints each matching line.
 *    SEARCH_MODE_DEL - copies all valid, non-matching lines to tmp_fp.
 *
 * In all cases, the number of matches is returned.  If rt is NULL, all routes
 * matching the global af value are considered matching.
 */
int
search_rtfile(FILE *fp, FILE *temp_fp, rtcmd_irep_t *rt, search_mode_t mode)
{
	char *tmp_buf;
	int match_cnt;
	boolean_t match;
	char file_line[BUF_SIZE + 4] = "add ";
	rtcmd_irep_t *thisrt;

	match_cnt = 0;

	/*
	 * Leave space at the beginning of file_line for "add ".
	 */
	while (fgets(file_line + 4, BUF_SIZE, fp) != NULL) {

		if (file_line[4] == '#' || file_line[4] == '\n') {
			/* Handle comments and blank lines */
			if (mode == SEARCH_MODE_DEL &&
			    fputs(file_line + 4, temp_fp) == EOF) {
				quit(gettext(
				    "route: failed to write to temp file"),
				    errno);
			}
			continue;
		}
		thisrt = new_rtcmd_irep();
		/*
		 * thisrt->ri_af defaults to whatever address family happens
		 * to be set in the global af, but routes in the persistent
		 * route file must be treated as AF_INET by default.
		 */
		thisrt->ri_af = AF_INET;

		exit_on_error = B_FALSE;
		tmp_buf = strdup(file_line);
		/* args_to_rtcmd() will mangle the string passed. */
		if (!args_to_rtcmd(thisrt, NULL, tmp_buf)) {
			/* There was an error in args_to_rtcmd() or helpers */
			del_rtcmd_irep(thisrt);
			free(tmp_buf);
			continue;
		}
		exit_on_error = B_TRUE;
		free(tmp_buf);

		if (thisrt->ri_gate_str == NULL) {
			del_rtcmd_irep(thisrt);
			continue;
		}
		match = (rt == NULL) ? (thisrt->ri_af == af) :
		    compare_rtcmd(rt, thisrt);

		if (match) match_cnt++;
		if (match && mode == SEARCH_MODE_PRINT) {
			(void) printf("persistent: route %s", file_line);
		}
		if (match && mode == SEARCH_MODE_DEL) {
			thisrt->ri_cmd = RTM_DELETE;
			print_rtcmd_short(stdout, thisrt, B_FALSE, B_TRUE);
			(void) printf("\n");
		}
		del_rtcmd_irep(thisrt);

		if (!match && mode == SEARCH_MODE_DEL &&
		    fputs(file_line + 4, temp_fp) == EOF) {
			quit(gettext("failed to write to temp file"),
			    errno);
		}
	}
	return (match_cnt);
}

/*
 * Perform the route operation given in argv on the persistent route file.
 * If do_flush is set, the persistent route file is flushed of all routes
 * matching the global family, and the arguments are ignored.
 */
void
save_route(int argc, char **argv, int do_flush)
{
	rtcmd_irep_t *rt;
	int perm_fd;
	FILE *perm_fp;
	FILE *temp_fp;
	mode_t fmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
	struct flock lock;
	struct stat st;
	const char commentstr[] =
	    "# File generated by route(1M) - do not edit.\n";

	perm_fd = open(perm_file, O_RDWR | O_CREAT, fmode);
	if (perm_fd == -1 || fstat(perm_fd, &st) == -1)
		quit("failed to open route file", errno);

	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;
	if (fcntl(perm_fd, F_SETLK, &lock) != 0) {
		quit(gettext("failed to lock route file"), errno);
		/* NOTREACHED */
	}
	if (st.st_size == 0 &&
	    write(perm_fd, commentstr, sizeof (commentstr) - 1) !=
	    sizeof (commentstr) - 1)
		quit(gettext("failed to open route file"), errno);

	if ((perm_fp = fdopen(perm_fd, "r+")) == NULL) {
		quit(gettext("failed to open route file"), errno);
		/* NOTREACHED */
	}

	if (!do_flush) {
		rt = new_rtcmd_irep();
		(void) args_to_rtcmd(rt, argv, NULL);
	}
	if (do_flush || rt->ri_cmd == RTM_DELETE) {
		if ((temp_fp = fopen(temp_file, "w")) == NULL) {
			quit(gettext("failed to open temp file"), errno);
			/* NOTREACHED */
		}
	}
	if (do_flush) {
		(void) search_rtfile(perm_fp, temp_fp, NULL, SEARCH_MODE_DEL);
		if (fclose(temp_fp) != 0 || rename(temp_file, perm_file) != 0) {
			quit(gettext("failed to update route file"), errno);
			/* NOTREACHED */
		}
		(void) fclose(perm_fp);
		return;
	}

	switch (rt->ri_cmd) {
	case RTM_ADD:
		if (search_rtfile(perm_fp, NULL, rt, SEARCH_MODE_NULL) > 0) {
			/* Route is already in the file */
			print_rtcmd_short(stderr, rt, B_FALSE, B_TRUE);
			(void) fprintf(stderr, ": entry exists\n");
			exit(1);
		}
		write_to_rtfile(perm_fp, argc - 1, argv + 1);
		print_rtcmd_short(stdout, rt, B_FALSE, B_TRUE);
		(void) printf("\n");
		break;

	case RTM_CHANGE:
		syntax_error(
		    gettext("route: change command not supported with -p\n"));
		/* NOTREACHED */

	case RTM_DELETE:
		if (search_rtfile(perm_fp, temp_fp, rt, SEARCH_MODE_DEL) <= 0) {
			/* Route not found */
			print_rtcmd_short(stderr, rt, B_FALSE, B_TRUE);
			(void) fprintf(stderr, gettext(": not in file\n"));
			exit(1);
		}
		if (fclose(temp_fp) != 0 || rename(temp_file, perm_file) != 0) {
			quit(gettext("failed to update route file"), errno);
			/* NOTREACHED */
		}
		break;

	case RTM_GET:
		if (search_rtfile(perm_fp, temp_fp, rt, SEARCH_MODE_PRINT) <=
		    0) {
			print_rtcmd_short(stdout, rt, B_FALSE, B_TRUE);
			(void) printf(gettext(": not in file\n"));
		}
		break;

	default:
		quit(gettext("Internal Error"), EINVAL);
		/* NOTREACHED */
	}

	/*
	 * Closing the file unlocks it.
	 */
	(void) fclose(perm_fp);
}

int
show_saved_routes(int argc)
{
	int perm_fd;
	FILE *perm_fp;
	struct flock lock;
	int count = 0;

	if (argc != 1) {
		syntax_error(gettext("route: invalid arguments for show\n"));
	}

	perm_fd = open(perm_file, O_RDONLY, 0);

	if (perm_fd == -1) {
		if (errno == ENOENT) {
			(void) printf("No persistent routes are defined\n");
			return (0);
		} else {
			quit(gettext("failed to open route file"), errno);
		}
	}
	lock.l_type = F_RDLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;
	if (fcntl(perm_fd, F_SETLK, &lock) != 0) {
		quit(gettext("failed to lock route file"),
		    errno);
		/* NOTREACHED */
	}
	if ((perm_fp = fdopen(perm_fd, "r")) == NULL) {
		quit(gettext("failed to open route file"), errno);
		/* NOTREACHED */
	}
	count += search_rtfile(perm_fp, NULL, NULL, SEARCH_MODE_PRINT);
	(void) fseek(perm_fp, 0, SEEK_SET);
	af = AF_INET6;
	count += search_rtfile(perm_fp, NULL, NULL, SEARCH_MODE_PRINT);

	if (count == 0)
		(void) printf("No persistent routes are defined\n");

	(void) fclose(perm_fp);
	return (0);
}

int
newroute(char **argv)
{
	rtcmd_irep_t *newrt;
	int ret, attempts, oerrno;
	char *err;
	char obuf[INET6_ADDRSTRLEN];
#define	hp (newrt->ri_gate_hp)

	newrt = new_rtcmd_irep();
	(void) args_to_rtcmd(newrt, argv, NULL);

	if (newrt->ri_cmd != RTM_GET && !tflag) {
		/* Don't want to read back our messages */
		(void) shutdown(s, 0);
	}
	if (newrt->ri_addrs & RTA_IFP) {
		newrt->ri_ifp.sdl.sdl_index = if_nametoindex(newrt->ri_ifp_str);
		if (newrt->ri_ifp.sdl.sdl_index == 0) {
			if (errno != ENXIO) {
				quit("if_nametoindex", errno);
			} else {
				(void) fprintf(stderr,
				    gettext("route: %s: no such interface\n"),
				    newrt->ri_ifp_str);
				exit(1);
			}
		}
		newrt->ri_ifp.sdl.sdl_family = AF_LINK;
	}
	for (attempts = 1; ; attempts++) {
		errno = 0;
		if ((ret = rtmsg(newrt)) == 0)
			break;
		if (errno != ENETUNREACH && errno != ESRCH)
			break;
		if ((newrt->ri_addrs & RTA_GATEWAY) && hp != NULL &&
		    hp->h_addr_list[attempts] != NULL) {
			switch (af) {
			case AF_INET:
				(void) memmove(&newrt->ri_gate.sin.sin_addr,
				    hp->h_addr_list[attempts], hp->h_length);
				continue;
			case AF_INET6:
				(void) memmove(&newrt->ri_gate.sin6.sin6_addr,
				    hp->h_addr_list[attempts], hp->h_length);
				continue;
			}
		}
		break;
	}
	oerrno = errno;

	if (newrt->ri_cmd != RTM_GET) {
		print_rtcmd_short(stdout, newrt, (ret == 0), B_FALSE);
		if (ret == 0)
			(void) printf("\n");
	} else if (ret != 0) {
		/*
		 * Note: there is nothing additional to print for get
		 * if ret == 0.
		 */
		if (nflag) {
			switch (newrt->ri_af) {
			case AF_INET:
				(void) printf(" %s",
				    inet_ntoa(newrt->ri_dst.sin.sin_addr));
				break;
			case AF_INET6:
				if (inet_ntop(AF_INET6,
					(void *)&newrt->ri_dst.sin6.sin6_addr,
					obuf, INET6_ADDRSTRLEN) != NULL) {
					(void) printf(" %s", obuf);
					break;
				}
				/* FALLTHROUGH */
			default:
				(void) printf("%s", newrt->ri_dest_str);
				break;
			}
		} else {
			(void) printf("%s", newrt->ri_dest_str);
		}
	}

	if (ret != 0) {
		switch (oerrno) {
		case ESRCH:
			err = "not in table";
			break;
		case EBUSY:
			err = "entry in use";
			break;
		case ENOBUFS:
			err = "routing table overflow";
			break;
		case EEXIST:
			err = "entry exists";
			break;
		case EPERM:
			err = "insufficient privileges";
			break;
		default:
			err = strerror(oerrno);
			break;
		}
		(void) printf(": %s\n", err);
	}

	del_rtcmd_irep(newrt);

	return (oerrno);
#undef hp
}


/*
 * Convert a network number to the corresponding IP address.
 * If the RTA_NETMASK hasn't been specified yet set it based
 * on the class of address.
 */
static void
inet_makenetandmask(rtcmd_irep_t *rcip, in_addr_t net, struct sockaddr_in *sin)
{
	in_addr_t addr, mask;

	if (net == 0) {
		mask = addr = 0;
	} else if (net < 128) {
		addr = net << IN_CLASSA_NSHIFT;
		mask = IN_CLASSA_NET;
	} else if (net < 65536) {
		addr = net << IN_CLASSB_NSHIFT;
		mask = IN_CLASSB_NET;
	} else if (net < 16777216L) {
		addr = net << IN_CLASSC_NSHIFT;
		mask = IN_CLASSC_NET;
	} else {
		addr = net;
		if ((addr & IN_CLASSA_HOST) == 0)
			mask =  IN_CLASSA_NET;
		else if ((addr & IN_CLASSB_HOST) == 0)
			mask =  IN_CLASSB_NET;
		else if ((addr & IN_CLASSC_HOST) == 0)
			mask =  IN_CLASSC_NET;
		else {
			if (IN_CLASSA(addr))
				mask =  IN_CLASSA_NET;
			else if (IN_CLASSB(addr))
				mask =  IN_CLASSB_NET;
			else if (IN_CLASSC(addr))
				mask =  IN_CLASSC_NET;
			else
				mask = IP_HOST_MASK;
			mask = inet_makesubnetmask(addr, mask);
		}
	}
	sin->sin_addr.s_addr = htonl(addr);

	/* Class E default mask is 32 */
	if (IN_CLASSE(addr))
		mask = IN_CLASSE_NET;

	if (!(rcip->ri_addrs & RTA_NETMASK)) {
		rcip->ri_addrs |= RTA_NETMASK;
		sin = &rcip->ri_mask.sin;
		sin->sin_addr.s_addr = htonl(mask);
		sin->sin_family = AF_INET;
	}
}

static in_addr_t
inet_makesubnetmask(in_addr_t addr, in_addr_t mask)
{
	int n;
	struct ifconf ifc;
	struct ifreq ifreq;
	struct ifreq *ifr;
	struct sockaddr_in *sin;
	char *buf;
	int numifs;
	size_t bufsize;
	int iosoc;
	in_addr_t if_addr, if_mask;
	in_addr_t if_subnetmask = 0;
	short if_flags;

	if (mask == 0)
		return (0);
	if ((iosoc = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		quit("socket", errno);
	if (ioctl(iosoc, SIOCGIFNUM, (char *)&numifs) < 0)
		quit("ioctl", errno);
	bufsize = numifs * sizeof (struct ifreq);
	buf = malloc(bufsize);
	if (buf == NULL)
		quit("malloc", errno);
	(void) memset(&ifc, 0, sizeof (ifc));
	ifc.ifc_len = bufsize;
	ifc.ifc_buf = buf;
	if (ioctl(iosoc, SIOCGIFCONF, (char *)&ifc) < 0)
		quit("ioctl (get interface configuration)", errno);
	/* Let's check to see if this is maybe a local subnet route. */
	ifr = ifc.ifc_req;
	for (n = ifc.ifc_len / sizeof (struct ifreq); n > 0; n--, ifr++) {
		ifreq = *ifr;
		/* LINTED */
		sin = (struct sockaddr_in *)&ifr->ifr_addr;
		if_addr = ntohl(sin->sin_addr.s_addr);

		if (ioctl(iosoc, SIOCGIFFLAGS, (char *)&ifreq) < 0)
			quit("ioctl (get interface flags)", errno);
		if ((ifreq.ifr_flags & IFF_UP) == 0)
			continue;
		if_flags = ifreq.ifr_flags;

		if (ioctl(iosoc, SIOCGIFNETMASK, (char *)&ifreq) < 0)
			quit("ioctl (get netmask)", errno);
		/* LINTED */
		sin = (struct sockaddr_in *)&ifreq.ifr_addr;
		if_mask = ntohl(sin->sin_addr.s_addr);
		if ((if_addr & mask) == (addr & mask)) {
			/*
			 * Don't trust pt-pt interfaces if there are
			 * other interfaces.
			 */
			if (if_flags & IFF_POINTOPOINT) {
				if_subnetmask = if_mask;
				continue;
			}
			/*
			 * Fine.  Just assume the same net mask as the
			 * directly attached subnet interface is using.
			 */
			return (if_mask);
		}
	}
	if (if_subnetmask != 0)
		return (if_subnetmask);
	return (mask);
}

/*
 * Interpret an argument as a network address of some kind.
 *
 * If the address family is one looked up in getaddr() using one of the
 * getipnodebyX() functions (currently only AF_INET6), then callers should
 * freehostent() the returned "struct hostent" pointer if one was passed in.
 *
 * If exit_on_error is true, this function will cause route to exit on error by
 * calling syntax_error().  Otherwise, it returns B_TRUE on success or B_FALSE
 * on failure.
 */
static boolean_t
getaddr(rtcmd_irep_t *rcip, int which, char *s, addr_type_t atype)
{
	su_t *su;
	struct hostent **hpp;
	struct hostent *hp;
	int masklen;

	if (which == RTA_GATEWAY) {
		hpp = &(rcip->ri_gate_hp);
	} else {
		hpp = &hp;
	}
	*hpp = NULL;

	rcip->ri_addrs |= which;
	switch (which) {
	case RTA_DST:
		save_string(&rcip->ri_dest_str, s);
		su = &rcip->ri_dst;
		su->sa.sa_family = rcip->ri_af;
		break;
	case RTA_GATEWAY:
		save_string(&rcip->ri_gate_str, s);
		su = &rcip->ri_gate;
		su->sa.sa_family = rcip->ri_af;
		break;
	case RTA_NETMASK:
		su = &rcip->ri_mask;
		su->sa.sa_family = rcip->ri_af;
		break;
	case RTA_IFP:
		save_string(&rcip->ri_ifp_str, s);
		return (B_TRUE);
		/*
		 * RTA_SRC has overloaded meaning. It can represent the
		 * src address of incoming or outgoing packets.
		 */
	case RTA_IFA:
		su = &rcip->ri_ifa;
		su->sa.sa_family = rcip->ri_af;
		break;
	case RTA_SRC:
		su = &rcip->ri_src;
		su->sa.sa_family = rcip->ri_af;
		break;
	default:
		/* NOTREACHED */
		quit(gettext("Internal Error"), EINVAL);
		/* NOTREACHED */
	}
	if (strcmp(s, "default") == 0) {
		if (which == RTA_DST) {
			return (getaddr(rcip, RTA_NETMASK, s, ADDR_TYPE_NET));
		}
		if (which == RTA_SRC) {
			return (B_TRUE);
		}
		return (B_TRUE);
	}
	switch (rcip->ri_af) {
	case AF_LINK:
		link_addr(s, &su->sdl);
		return (B_TRUE);
	case PF_ROUTE:
		sockaddr(s, &su->sa);
		return (B_TRUE);
	case AF_INET6:
		switch (which) {
		case RTA_DST:
			if (!in6_getaddr(s, &su->sin6, &masklen, hpp)) {
				return (B_FALSE);
			}
			if (masklen != NO_PREFIX) {
				(void) memset(&rcip->ri_mask.sin6.sin6_addr, 0,
				    sizeof (rcip->ri_mask.sin6.sin6_addr));
				if (!in_prefixlentomask(masklen, IPV6_ABITS,
				    (uchar_t *)&rcip->ri_mask.sin6.sin6_addr)) {
					syntax_error(gettext(
					    "route: bad prefix length: %d\n"),
					    masklen);
					return (B_FALSE);
				}
				rcip->ri_mask.sin6.sin6_family = rcip->ri_af;
				rcip->ri_addrs |= RTA_NETMASK;
			}
			return (B_TRUE);
		case RTA_GATEWAY:
		case RTA_IFA:
		case RTA_SRC:
			return (in6_getaddr(s, &su->sin6, NULL, hpp));
		case RTA_NETMASK:
			syntax_error(
			    gettext("route: -netmask not supported for IPv6: "
			    "use <prefix>/<prefix-length> instead\n"));
			return (B_FALSE);
		default:
			quit(gettext("Internal Error"), EINVAL);
			/* NOTREACHED */
		}
	case AF_INET:
		switch (which) {
		case RTA_DST:
			if (!in_getaddr(s, &su->sin, &masklen, which, hpp,
			    atype, rcip)) {
				return (B_FALSE);
			}
			if (masklen != NO_PREFIX) {
				(void) memset(&rcip->ri_mask.sin.sin_addr, 0,
				    sizeof (rcip->ri_mask.sin.sin_addr));
				if (!in_prefixlentomask(masklen, IP_ABITS,
				    (uchar_t *)&rcip->ri_mask.sin.sin_addr)) {
					syntax_error(gettext(
					    "route: bad prefix length: %d\n"),
					    masklen);
					return (B_FALSE);
				}
				rcip->ri_mask.sin.sin_family = rcip->ri_af;
				rcip->ri_addrs |= RTA_NETMASK;
			}
			return (B_TRUE);
		case RTA_GATEWAY:
		case RTA_IFA:
		case RTA_NETMASK:
		case RTA_SRC:
			return (in_getaddr(s, &su->sin, NULL, which, hpp, atype,
			    rcip));
		default:
			quit(gettext("Internal Error"), EINVAL);
			/* NOTREACHED */
		}
	default:
		quit(gettext("Internal Error"), EINVAL);
		/* NOTREACHED */
	}
	return (B_TRUE);
}

/*
 * Interpret an argument as an IPv4 network address of some kind,
 * returning B_TRUE on success or B_FALSE on failure.
 * This function will cause an exit() on failure if exit_on_failure is set.
 *
 * Note that this tries host interpretation before network interpretation,
 * except when -net has been given and the destination address is being parsed.
 *
 * If the plenp argument is non-NULL, allow <addr>/<n> syntax and
 * pass out <n> in *plenp.
 * If <n> doesn't parse return BAD_ADDR as *plenp.
 * If no /<n> is present return NO_PREFIX as *plenp.
 */
static boolean_t
in_getaddr(char *s, struct sockaddr_in *sin, int *plenp, int which,
    struct hostent **hpp, addr_type_t atype, rtcmd_irep_t *rcip)
{
	struct hostent *hp;
	struct netent *np;
	in_addr_t val;
	char str[BUFSIZ];

	(void) strlcpy(str, s, sizeof (str));

	/*
	 * If plenp is non-NULL, /<n> syntax for netmask is allowed.
	 */
	if (plenp != NULL) {
		char *cp;

		*plenp = in_getprefixlen(str, IP_ABITS);
		if (*plenp == BAD_ADDR)
			return (B_FALSE);
		cp = strchr(str, '/');
		if (cp != NULL)
			*cp = '\0';
	} else if (strchr(str, '/') != NULL) {
		syntax_error(gettext("route: %s: unexpected '/'\n"), str);
		return (B_FALSE);
	}

	(void) memset(sin, 0, sizeof (*sin));
	sin->sin_family = AF_INET;

	/*
	 * Handle 255.255.255.255 as a special case first.
	 */
	if (strcmp(str, "255.255.255.255") == 0) {
		sin->sin_addr.s_addr = INADDR_BROADCAST;
		return (B_TRUE);
	}

	val = inet_addr(str);
	if (val != (in_addr_t)-1) {
		/* Numeric address */
		sin->sin_addr.s_addr = val;
		if (which == RTA_DST) {
			if (atype == ADDR_TYPE_NET ||
			    (atype == ADDR_TYPE_ANY &&
			    inet_lnaof(sin->sin_addr) == INADDR_ANY)) {
				/* This looks like a network address. */
				inet_makenetandmask(rcip, ntohl(val),
					    sin);
			}
		}
		return (B_TRUE);
	}
	/* Host or net name */
	if (which != RTA_DST || atype != ADDR_TYPE_NET) {
		/* A host name is allowed. */
		if ((hp = gethostbyname(str)) != NULL) {
			*hpp = hp;
			(void) memmove(&sin->sin_addr, hp->h_addr,
			    hp->h_length);
			return (B_TRUE);
		}
	}
	if (atype != ADDR_TYPE_HOST) {
		/* A network name is allowed */
		if ((np = getnetbyname(str)) != NULL &&
		    (val = np->n_net) != 0) {
			if (which == RTA_DST) {
				inet_makenetandmask(rcip, val, sin);
			}
			return (B_TRUE);
		}
	}
	syntax_error(gettext("%s: bad value\n"), s);
	return (B_FALSE);
}

/*
 * Interpret an argument as an IPv6 network address of some kind,
 * returning B_TRUE on success or B_FALSE on failure.
 * This function will cause an exit() on failure if exit_on_failure is set.
 *
 * If the last argument is non-NULL allow a <addr>/<n> syntax and
 * pass out <n> in *plenp.
 * If <n> doesn't parse return BAD_ADDR as *plenp.
 * If no /<n> is present return NO_PREFIX as *plenp.
 */
static boolean_t
in6_getaddr(char *s, struct sockaddr_in6 *sin6, int *plenp,
    struct hostent **hpp)
{
	struct hostent *hp;
	char str[BUFSIZ];
	int error_num;

	(void) strlcpy(str, s, sizeof (str));

	/*
	 * If plenp is non-NULL, /<n> syntax for netmask is allowed.
	 */
	if (plenp != NULL) {
		char *cp;

		*plenp = in_getprefixlen(str, IPV6_ABITS);
		if (*plenp == BAD_ADDR)
			return (B_FALSE);
		cp = strchr(str, '/');
		if (cp != NULL)
			*cp = '\0';
	} else if (strchr(str, '/') != NULL) {
		syntax_error(gettext("route: %s: unexpected '/'\n"), str);
		return (B_FALSE);
	}

	(void) memset(sin6, 0, sizeof (struct sockaddr_in6));
	sin6->sin6_family = AF_INET6;

	hp = getipnodebyname(str, AF_INET6, 0, &error_num);
	if (hp != NULL) {
		*hpp = hp;
		(void) memmove(&sin6->sin6_addr, hp->h_addr, hp->h_length);
		return (B_TRUE);
	}
	if (error_num == TRY_AGAIN) {
		/*
		 * This isn't a problem if we aren't going to use the address
		 * right away.
		 */
		if (!exit_on_error) {
			return (B_TRUE);
		}
		syntax_error(gettext("route: %s: bad address (try "
		    "again later)\n"), s);
		return (B_FALSE);
	}
	syntax_error(gettext("route: %s: bad address\n"), s);
	return (B_FALSE);
}

/*
 * Parse <addr>/<n> syntax and return the integer n.
 * If <addr> is missing or <n> is not a valid integer, this function calls
 * syntax_error() and returns BAD_ADDR.
 * if n is not between 0 and max_plen inclusive, this functions calls
 * syntax_error() and returns BAD_ADDR.
 * If /<n> is not present, this function returns NO_PREFIX.
 * The string addr is not modified.
 */
int
in_getprefixlen(char *addr, int max_plen)
{
	int prefixlen;
	char *str, *end;

	str = strchr(addr, '/');
	if (str == addr) {
		syntax_error(gettext("route: %s: unexpected '/'\n"), addr);
		return (BAD_ADDR);
	}
	if (str == NULL)
		return (NO_PREFIX);
	str++;

	errno = 0;
	prefixlen = strtoul(str, &end, 10);
	if (errno != 0 || str == end) {
		syntax_error(gettext("route: bad prefix length %s\n"), str);
		return (BAD_ADDR);
	}
	if (prefixlen > max_plen) {
		syntax_error(gettext("route: prefix length %s out of range\n"),
		    str);
		return (BAD_ADDR);
	}
	return (prefixlen);
}

/*
 * Convert a prefix length to a mask.
 * Returns B_TRUE if ok. B_FALSE otherwise.
 * Assumes the mask array is zeroed by the caller.
 */
boolean_t
in_prefixlentomask(int prefixlen, int maxlen, uchar_t *mask)
{
	if (prefixlen < 0 || prefixlen > maxlen)
		return (B_FALSE);

	while (prefixlen > 0) {
		if (prefixlen >= 8) {
			*mask++ = 0xFF;
			prefixlen -= 8;
			continue;
		}
		*mask |= 1 << (8 - prefixlen);
		prefixlen--;
	}
	return (B_TRUE);
}

void
rtmonitor(int argc, char *argv[])
{
	int n;
	intmax_t msg[2048 / sizeof (intmax_t)];

	if (tflag)
		exit(0);
	verbose = B_TRUE;
	if (argc > 1) {
		argv++;
		if (argc == 2 && **argv == '-') {
			switch (keyword(*argv + 1)) {
			case K_INET:
				af = AF_INET;
				break;
			case K_LINK:
				af = AF_LINK;
				break;
			case K_INET6:
				af = AF_INET6;
				break;
			default:
				usage(*argv);
				/* NOTREACHED */
			}
		} else {
			usage(*argv);
		}
		(void) close(s);
		s = socket(PF_ROUTE, SOCK_RAW, af);
		if (s < 0)
			quit("socket", errno);
	}
	for (;;) {
		n = read(s, msg, sizeof (msg));
		if (n <= 0)
			quit("read", errno);
		(void) printf("got message of size %d\n", n);
		print_rtmsg((struct rt_msghdr *)msg, n);
	}
}

int
rtmsg(rtcmd_irep_t *newrt)
{
	static int seq;
	int rlen;
	char *cp = m_rtmsg.m_space;
	int l;

	errno = 0;
	(void) memset(&m_rtmsg, 0, sizeof (m_rtmsg));

	if (newrt->ri_cmd == RTM_GET) {
		newrt->ri_ifp.sa.sa_family = AF_LINK;
		newrt->ri_addrs |= RTA_IFP;
	}

#define	rtm m_rtmsg.m_rtm
	rtm.rtm_type = newrt->ri_cmd;
	rtm.rtm_flags = newrt->ri_flags;
	rtm.rtm_version = RTM_VERSION;
	rtm.rtm_seq = ++seq;
	rtm.rtm_addrs = newrt->ri_addrs;
	rtm.rtm_rmx = newrt->ri_metrics;
	rtm.rtm_inits = newrt->ri_inits;

#define	NEXTADDR(w, u) \
	if (newrt->ri_addrs & (w)) { \
		l = ROUNDUP_LONG(salen(&u.sa)); \
		(void) memmove(cp, &(u), l); \
		cp += l; \
		if (verbose) \
			sodump(&(u), #u); \
	}
	NEXTADDR(RTA_DST, newrt->ri_dst);
	NEXTADDR(RTA_GATEWAY, newrt->ri_gate);
	NEXTADDR(RTA_NETMASK, newrt->ri_mask);
	NEXTADDR(RTA_IFP, newrt->ri_ifp);
	NEXTADDR(RTA_IFA, newrt->ri_ifa);
	/*
	 * RTA_SRC has overloaded meaning. It can represent the
	 * src address of incoming or outgoing packets.
	 */
	NEXTADDR(RTA_SRC, newrt->ri_src);
#undef	NEXTADDR

	if (newrt->ri_rtsa_cnt > 0) {
		/* LINTED: aligned */
		rtm_ext_t *rtm_ext = (rtm_ext_t *)cp;
		tsol_rtsecattr_t *rtsecattr;

		rtm_ext->rtmex_type = RTMEX_GATEWAY_SECATTR;
		rtm_ext->rtmex_len = TSOL_RTSECATTR_SIZE(1);

		rtsecattr = (tsol_rtsecattr_t *)(rtm_ext + 1);
		rtsecattr->rtsa_cnt = 1;

		bcopy(&newrt->ri_rtsa, rtsecattr->rtsa_attr,
		    sizeof (newrt->ri_rtsa));
		cp = (char *)(rtsecattr->rtsa_attr + 1);
	}

	rtm.rtm_msglen = l = cp - (char *)&m_rtmsg;

	if (verbose)
		print_rtmsg(&rtm, l);
	if (debugonly)
		return (0);
	if ((rlen = write(s, (char *)&m_rtmsg, l)) < 0) {
		switch (errno) {
		case ESRCH:
		case EBUSY:
		case ENOBUFS:
		case EEXIST:
		case ENETUNREACH:
		case EHOSTUNREACH:
		case EPERM:
			break;
		default:
			perror(gettext("writing to routing socket"));
			break;
		}
		return (-1);
	} else if (rlen < (int)rtm.rtm_msglen) {
		(void) fprintf(stderr,
		    gettext("route: write to routing socket got only %d for "
		    "len\n"), rlen);
		return (-1);
	}
	if (newrt->ri_cmd == RTM_GET) {
		do {
			l = read(s, (char *)&m_rtmsg, sizeof (m_rtmsg));
		} while (l > 0 && (rtm.rtm_seq != seq || rtm.rtm_pid != pid));
		if (l < 0) {
			(void) fprintf(stderr,
			    gettext("route: read from routing socket: %s\n"),
			    strerror(errno));
		} else {
			print_getmsg(newrt, &rtm, l);
		}
	}
#undef rtm
	return (0);
}

static char *msgtypes[] = {
	"",
	"RTM_ADD: Add Route",
	"RTM_DELETE: Delete Route",
	"RTM_CHANGE: Change Metrics or flags",
	"RTM_GET: Report Metrics",
	"RTM_LOSING: Kernel Suspects Partitioning",
	"RTM_REDIRECT: Told to use different route",
	"RTM_MISS: Lookup failed on this address",
	"RTM_LOCK: fix specified metrics",
	"RTM_OLDADD: caused by SIOCADDRT",
	"RTM_OLDDEL: caused by SIOCDELRT",
	"RTM_RESOLVE: Route created by cloning",
	"RTM_NEWADDR: address being added to iface",
	"RTM_DELADDR: address being removed from iface",
	"RTM_IFINFO: iface status change",
	0,
};

#define	NMSGTYPES (sizeof (msgtypes) / sizeof (msgtypes[0]))

static char metricnames[] =
"\011pksent\010rttvar\7rtt\6ssthresh\5sendpipe\4recvpipe\3expire\2hopcount"
	"\1mtu";
static char routeflags[] =
"\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE\010MASK_PRESENT"
	"\011CLONING\012XRESOLVE\013LLINFO\014STATIC\015BLACKHOLE"
	"\016PRIVATE\017PROTO2\020PROTO1\021MULTIRT\022SETSRC";
static char ifnetflags[] =
"\1UP\2BROADCAST\3DEBUG\4LOOPBACK\5PTP\6NOTRAILERS\7RUNNING\010NOARP"
	"\011PPROMISC\012ALLMULTI\013INTELLIGENT\014MULTICAST"
	"\015MULTI_BCAST\016UNNUMBERED\017DHCP\020PRIVATE"
	"\021NOXMIT\022NOLOCAL\023DEPRECATED\024ADDRCONF"
	"\025ROUTER\026NONUD\027ANYCAST\030NORTEXCH\031IPv4\032IPv6"
	"\034NOFAILOVER\035FAILED\036STANDBY\037INACTIVE\040OFFLINE"
	"\041XRESOLV\042COS\043PREFERRED\044TEMPORARY\045FIXEDMTU\046VIRTUAL"
	"\047DUPLICATE";
static char addrnames[] =
"\1DST\2GATEWAY\3NETMASK\4GENMASK\5IFP\6IFA\7AUTHOR\010BRD\011SRC";

void
print_rtmsg(struct rt_msghdr *rtm, int msglen)
{
	struct if_msghdr *ifm;
	struct ifa_msghdr *ifam;

	if (!verbose)
		return;
	if (rtm->rtm_version != RTM_VERSION) {
		(void) printf("routing message version %d not understood\n",
		    rtm->rtm_version);
		return;
	}
	if (rtm->rtm_msglen != msglen) {
		(void) printf("message length mismatch, in packet %d, "
		    "returned %d\n",
		    rtm->rtm_msglen, msglen);
		if (msglen > rtm->rtm_msglen)
			msglen = rtm->rtm_msglen;
	}
	/*
	 * Since rtm->rtm_type is unsigned, we'll just check the case of zero
	 * and the upper-bound of (NMSGTYPES - 1).
	 */
	if (rtm->rtm_type == 0 || rtm->rtm_type >= (NMSGTYPES - 1)) {
		(void) printf("routing message type %d not understood\n",
		    rtm->rtm_type);
		return;
	}
	(void) printf("%s: len %d, ", msgtypes[rtm->rtm_type], msglen);
	switch (rtm->rtm_type) {
	case RTM_IFINFO:
		ifm = (struct if_msghdr *)rtm;
		(void) printf("if# %d, flags:", ifm->ifm_index);
		bprintf(stdout, ifm->ifm_flags, ifnetflags);
		pmsg_addrs((const char *)(ifm + 1), msglen - sizeof (*ifm),
		    ifm->ifm_addrs);
		break;
	case RTM_NEWADDR:
	case RTM_DELADDR:
		ifam = (struct ifa_msghdr *)rtm;
		(void) printf("metric %d, flags:", ifam->ifam_metric);
		bprintf(stdout, ifam->ifam_flags, routeflags);
		pmsg_addrs((const char *)(ifam + 1), msglen - sizeof (*ifam),
		    ifam->ifam_addrs);
		break;
	default:
		(void) printf("pid: %ld, seq %d, errno %d, flags:",
			rtm->rtm_pid, rtm->rtm_seq, rtm->rtm_errno);
		bprintf(stdout, rtm->rtm_flags, routeflags);
		pmsg_common(rtm, msglen);
		break;
	}
}

void
print_getmsg(rtcmd_irep_t *req_rt, struct rt_msghdr *rtm, int msglen)
{
	struct sockaddr *dst = NULL, *gate = NULL, *mask = NULL, *src = NULL;
	struct sockaddr_dl *ifp = NULL;
	struct sockaddr *sa;
	char *cp;
	int i;

	(void) printf("   route to: %s\n", routename(&req_rt->ri_dst.sa));
	if (rtm->rtm_version != RTM_VERSION) {
		(void) fprintf(stderr,
		    gettext("routing message version %d not understood\n"),
		    rtm->rtm_version);
		return;
	}
	if (rtm->rtm_msglen > (ushort_t)msglen) {
		(void) fprintf(stderr,
		    gettext("message length mismatch, in packet %d, "
			"returned %d\n"), rtm->rtm_msglen, msglen);
	}
	if (rtm->rtm_errno)  {
		(void) fprintf(stderr, "RTM_GET: %s (errno %d)\n",
		    strerror(rtm->rtm_errno), rtm->rtm_errno);
		return;
	}
	cp = ((char *)(rtm + 1));
	if (rtm->rtm_addrs != 0) {
		for (i = 1; i != 0; i <<= 1) {
			if (i & rtm->rtm_addrs) {
				/* LINTED */
				sa = (struct sockaddr *)cp;
				switch (i) {
				case RTA_DST:
					dst = sa;
					break;
				case RTA_GATEWAY:
					gate = sa;
					break;
				case RTA_NETMASK:
					mask = sa;
					break;
				case RTA_IFP:
					if (sa->sa_family == AF_LINK &&
					    ((struct sockaddr_dl *)sa)->
						sdl_nlen != 0)
						ifp = (struct sockaddr_dl *)sa;
					break;
				case RTA_SRC:
					src = sa;
					break;
				}
				ADVANCE(cp, sa);
			}
		}
	}
	if (dst != NULL && mask != NULL)
		mask->sa_family = dst->sa_family;	/* XXX */
	if (dst != NULL)
		(void) printf("destination: %s\n", routename(dst));
	if (mask != NULL) {
		boolean_t savenflag = nflag;

		nflag = B_TRUE;
		(void) printf("       mask: %s\n", routename(mask));
		nflag = savenflag;
	}
	if (gate != NULL && rtm->rtm_flags & RTF_GATEWAY)
		(void) printf("    gateway: %s\n", routename(gate));
	if (src != NULL && rtm->rtm_flags & RTF_SETSRC)
		(void) printf("     setsrc: %s\n", routename(src));
	if (ifp != NULL) {
		if (verbose) {
			int i;

			(void) printf("  interface: %.*s index %d address ",
			    ifp->sdl_nlen, ifp->sdl_data, ifp->sdl_index);
			for (i = ifp->sdl_nlen;
			    i < ifp->sdl_nlen + ifp->sdl_alen;
			    i++) {
				(void) printf("%02x ",
				    ifp->sdl_data[i] & 0xFF);
			}
			(void) printf("\n");
		} else {
			(void) printf("  interface: %.*s\n",
			    ifp->sdl_nlen, ifp->sdl_data);
		}
	}
	(void) printf("      flags: ");
	bprintf(stdout, rtm->rtm_flags, routeflags);

#define	lock(f)	((rtm->rtm_rmx.rmx_locks & RTV_ ## f) ? 'L' : ' ')
#define	msec(u)	(((u) + 500) / 1000)		/* usec to msec */

	(void) printf("\n%s\n", " recvpipe  sendpipe  ssthresh    rtt,ms "
	    "rttvar,ms  hopcount      mtu     expire");
	(void) printf("%8d%c ", rtm->rtm_rmx.rmx_recvpipe, lock(RPIPE));
	(void) printf("%8d%c ", rtm->rtm_rmx.rmx_sendpipe, lock(SPIPE));
	(void) printf("%8d%c ", rtm->rtm_rmx.rmx_ssthresh, lock(SSTHRESH));
	(void) printf("%8d%c ", msec(rtm->rtm_rmx.rmx_rtt), lock(RTT));
	(void) printf("%8d%c ", msec(rtm->rtm_rmx.rmx_rttvar), lock(RTTVAR));
	(void) printf("%8d%c ", rtm->rtm_rmx.rmx_hopcount, lock(HOPCOUNT));
	(void) printf("%8d%c ", rtm->rtm_rmx.rmx_mtu, lock(MTU));
	if (rtm->rtm_rmx.rmx_expire)
		rtm->rtm_rmx.rmx_expire -= time(0);
	(void) printf("%8d%c", rtm->rtm_rmx.rmx_expire, lock(EXPIRE));
#undef lock
#undef msec
#define	RTA_IGN	\
	(RTA_DST|RTA_GATEWAY|RTA_NETMASK|RTA_IFP|RTA_IFA|RTA_BRD|RTA_SRC)
	if (verbose) {
		pmsg_common(rtm, msglen);
	} else {
		const char *sptr, *endptr;
		const struct sockaddr *sa;
		uint_t addrs;

		/* Not verbose; just print out the exceptional cases */
		if (rtm->rtm_addrs &~ RTA_IGN) {
			(void) printf("\nsockaddrs: ");
			bprintf(stdout, rtm->rtm_addrs, addrnames);
		}
		sptr = (const char *)(rtm + 1);
		endptr = (const char *)rtm + msglen;
		addrs = rtm->rtm_addrs;
		while (addrs != 0 && sptr + sizeof (*sa) <= endptr) {
			addrs &= addrs - 1;
			/* LINTED */
			sa = (const struct sockaddr *)sptr;
			ADVANCE(sptr, sa);
		}
		if (addrs == 0)
			pmsg_secattr(sptr, endptr - sptr, "    secattr: ");
		(void) putchar('\n');
	}
#undef	RTA_IGN
}

static void
pmsg_common(const struct rt_msghdr *rtm, size_t msglen)
{
	(void) printf("\nlocks: ");
	bprintf(stdout, (int)rtm->rtm_rmx.rmx_locks, metricnames);
	(void) printf(" inits: ");
	bprintf(stdout, (int)rtm->rtm_inits, metricnames);
	pmsg_addrs((const char *)(rtm + 1), msglen - sizeof (*rtm),
	    rtm->rtm_addrs);
}

static void
pmsg_addrs(const char *cp, size_t msglen, uint_t addrs)
{
	const struct sockaddr *sa;
	const char *maxptr;
	int i;

	if (addrs != 0) {
		(void) printf("\nsockaddrs: ");
		bprintf(stdout, addrs, addrnames);
		(void) putchar('\n');
		maxptr = cp + msglen;
		for (i = 1; i != 0 && cp + sizeof (*sa) <= maxptr; i <<= 1) {
			if (i & addrs) {
				/* LINTED */
				sa = (const struct sockaddr *)cp;
				(void) printf(" %s", routename(sa));
				ADVANCE(cp, sa);
			}
		}
		if (i != 0)
			msglen = 0;
		else
			msglen = maxptr - cp;
	}
	pmsg_secattr(cp, msglen, "secattr: ");
	(void) putchar('\n');
	(void) fflush(stdout);
}

void
bprintf(FILE *fp, int b, char *s)
{
	int i;
	boolean_t gotsome = B_FALSE;

	if (b == 0)
		return;
	while ((i = *s++) != 0) {
		if (b & (1 << (i - 1))) {
			if (!gotsome)
				i = '<';
			else
				i = ',';
			(void) putc(i, fp);
			gotsome = B_TRUE;
			for (; (i = *s) > ' '; s++)
				(void) putc(i, fp);
		} else {
			while (*s > ' ')
				s++;
		}
	}
	if (gotsome)
		(void) putc('>', fp);
}

int
keyword(const char *cp)
{
	struct keytab *kt = keywords;

	while (kt->kt_cp && strcmp(kt->kt_cp, cp))
		kt++;
	return (kt->kt_i);
}

void
sodump(su_t *su, char *which)
{
	static char obuf[INET6_ADDRSTRLEN];

	switch (su->sa.sa_family) {
	case AF_LINK:
		(void) printf("%s: link %s; ",
		    which, link_ntoa(&su->sdl));
		break;
	case AF_INET:
		(void) printf("%s: inet %s; ",
		    which, inet_ntoa(su->sin.sin_addr));
		break;
	case AF_INET6:
		if (inet_ntop(AF_INET6, (void *)&su->sin6.sin6_addr, obuf,
		    INET6_ADDRSTRLEN) != NULL) {
			(void) printf("%s: inet6 %s; ", which, obuf);
			break;
		}
		/* FALLTHROUGH */
	default:
		quit(gettext("Internal Error"), EINVAL);
		/* NOTREACHED */
	}
	(void) fflush(stdout);
}

/* States */
#define	VIRGIN	0
#define	GOTONE	1
#define	GOTTWO	2
#define	RESET	3
/* Inputs */
#define	DIGIT	(4*0)
#define	END	(4*1)
#define	DELIM	(4*2)
#define	LETTER	(4*3)

void
sockaddr(char *addr, struct sockaddr *sa)
{
	char *cp = (char *)sa;
	int size = salen(sa);
	char *cplim = cp + size;
	int byte = 0, state = VIRGIN, new;

	(void) memset(cp, 0, size);
	cp++;
	do {
		if ((*addr >= '0') && (*addr <= '9')) {
			new = *addr - '0';
		} else if ((*addr >= 'a') && (*addr <= 'f')) {
			new = *addr - 'a' + 10;
		} else if ((*addr >= 'A') && (*addr <= 'F')) {
			new = *addr - 'A' + 10;
		} else if (*addr == 0) {
			state |= END;
		} else {
			state |= DELIM;
		}
		addr++;
		switch (state /* | INPUT */) {
		case GOTTWO | DIGIT:
			*cp++ = byte;
			/* FALLTHROUGH */
		case VIRGIN | DIGIT:
			state = GOTONE; byte = new; continue;
		case GOTONE | DIGIT:
			state = GOTTWO; byte = new + (byte << 4); continue;
		default: /* | DELIM */
			state = VIRGIN; *cp++ = byte; byte = 0; continue;
		case GOTONE | END:
		case GOTTWO | END:
			*cp++ = byte;
			/* FALLTHROUGH */
		case VIRGIN | END:
			break;
		}
		break;
	} while (cp < cplim);
}

int
salen(const struct sockaddr *sa)
{
	switch (sa->sa_family) {
	case AF_INET:
		return (sizeof (struct sockaddr_in));
	case AF_LINK:
		return (sizeof (struct sockaddr_dl));
	case AF_INET6:
		return (sizeof (struct sockaddr_in6));
	default:
		return (sizeof (struct sockaddr));
	}
}

void
link_addr(const char *addr, struct sockaddr_dl *sdl)
{
	char *cp = sdl->sdl_data;
	char *cplim = sizeof (struct sockaddr_dl) + (char *)sdl;
	int byte = 0, state = VIRGIN, new;

	(void) memset(sdl, 0, sizeof (struct sockaddr_dl));
	sdl->sdl_family = AF_LINK;
	do {
		state &= ~LETTER;
		if ((*addr >= '0') && (*addr <= '9')) {
			new = *addr - '0';
		} else if ((*addr >= 'a') && (*addr <= 'f')) {
			new = *addr - 'a' + 10;
		} else if ((*addr >= 'A') && (*addr <= 'F')) {
			new = *addr - 'A' + 10;
		} else if (*addr == 0) {
			state |= END;
		} else if (state == VIRGIN &&
		    (((*addr >= 'A') && (*addr <= 'Z')) ||
		    ((*addr >= 'a') && (*addr <= 'z')))) {
			state |= LETTER;
		} else {
			state |= DELIM;
		}
		addr++;
		switch (state /* | INPUT */) {
		case VIRGIN | DIGIT:
		case VIRGIN | LETTER:
			*cp++ = addr[-1];
			continue;
		case VIRGIN | DELIM:
			state = RESET;
			sdl->sdl_nlen = cp - sdl->sdl_data;
			continue;
		case GOTTWO | DIGIT:
			*cp++ = byte;
			/* FALLTHROUGH */
		case RESET | DIGIT:
			state = GOTONE;
			byte = new;
			continue;
		case GOTONE | DIGIT:
			state = GOTTWO;
			byte = new + (byte << 4);
			continue;
		default: /* | DELIM */
			state = RESET;
			*cp++ = byte;
			byte = 0;
			continue;
		case GOTONE | END:
		case GOTTWO | END:
			*cp++ = byte;
			/* FALLTHROUGH */
		case RESET | END:
			break;
		}
		break;
	} while (cp < cplim);
	sdl->sdl_alen = cp - LLADDR(sdl);
}

static char hexlist[] = "0123456789abcdef";

char *
link_ntoa(const struct sockaddr_dl *sdl)
{
	static char obuf[64];
	char *out = obuf;
	int i;
	uchar_t *in = (uchar_t *)LLADDR(sdl);
	uchar_t *inlim = in + sdl->sdl_alen;
	boolean_t firsttime = B_TRUE;

	if (sdl->sdl_nlen) {
		(void) memcpy(obuf, sdl->sdl_data, sdl->sdl_nlen);
		out += sdl->sdl_nlen;
		if (sdl->sdl_alen)
			*out++ = ':';
	}
	while (in < inlim) {
		if (firsttime)
			firsttime = B_FALSE;
		else
			*out++ = '.';
		i = *in++;
		if (i > 0xf) {
			out[1] = hexlist[i & 0xf];
			i >>= 4;
			out[0] = hexlist[i];
			out += 2;
		} else {
			*out++ = hexlist[i];
		}
	}
	*out = 0;
	return (obuf);
}

static mib_item_t *
mibget(int sd)
{
	intmax_t		buf[512 / sizeof (intmax_t)];
	int			flags;
	int			i, j, getcode;
	struct strbuf		ctlbuf, databuf;
	struct T_optmgmt_req	*tor = (struct T_optmgmt_req *)buf;
	struct T_optmgmt_ack	*toa = (struct T_optmgmt_ack *)buf;
	struct T_error_ack	*tea = (struct T_error_ack *)buf;
	struct opthdr		*req;
	mib_item_t		*first_item = NULL;
	mib_item_t		*last_item  = NULL;
	mib_item_t		*temp;

	tor->PRIM_type = T_SVR4_OPTMGMT_REQ;
	tor->OPT_offset = sizeof (struct T_optmgmt_req);
	tor->OPT_length = sizeof (struct opthdr);
	tor->MGMT_flags = T_CURRENT;
	req = (struct opthdr *)&tor[1];
	req->level = MIB2_IP;		/* any MIB2_xxx value ok here */
	req->name  = 0;
	req->len   = 0;

	ctlbuf.buf = (char *)buf;
	ctlbuf.len = tor->OPT_length + tor->OPT_offset;
	flags = 0;
	if (putmsg(sd, &ctlbuf, NULL, flags) < 0) {
		perror("mibget: putmsg (ctl)");
		return (NULL);
	}
	/*
	 * each reply consists of a ctl part for one fixed structure
	 * or table, as defined in mib2.h.  The format is a T_OPTMGMT_ACK,
	 * containing an opthdr structure.  level/name identify the entry,
	 * len is the size of the data part of the message.
	 */
	req = (struct opthdr *)&toa[1];
	ctlbuf.maxlen = sizeof (buf);
	for (j = 1; ; j++) {
		flags = 0;
		getcode = getmsg(sd, &ctlbuf, NULL, &flags);
		if (getcode < 0) {
			perror("mibget: getmsg (ctl)");
			if (verbose) {
				(void) fprintf(stderr,
				    "#   level   name    len\n");
				i = 0;
				for (last_item = first_item; last_item != NULL;
				    last_item = last_item->next_item) {
					(void) printf("%d  %4ld   %5ld   %ld\n",
					    ++i, last_item->group,
					    last_item->mib_id,
					    last_item->length);
				}
			}
			break;
		}
		if (getcode == 0 &&
		    ctlbuf.len >= sizeof (struct T_optmgmt_ack) &&
		    toa->PRIM_type == T_OPTMGMT_ACK &&
		    toa->MGMT_flags == T_SUCCESS &&
		    req->len == 0) {
			if (verbose) {
				(void) printf("mibget getmsg() %d returned EOD "
				    "(level %lu, name %lu)\n", j, req->level,
				    req->name);
			}
			return (first_item);		/* this is EOD msg */
		}

		if (ctlbuf.len >= sizeof (struct T_error_ack) &&
		    tea->PRIM_type == T_ERROR_ACK) {
			(void) fprintf(stderr, gettext("mibget %d gives "
			    "T_ERROR_ACK: TLI_error = 0x%lx, UNIX_error = "
			    "0x%lx\n"), j, tea->TLI_error, tea->UNIX_error);
			errno = (tea->TLI_error == TSYSERR)
				? tea->UNIX_error : EPROTO;
			break;
		}

		if (getcode != MOREDATA ||
		    ctlbuf.len < sizeof (struct T_optmgmt_ack) ||
		    toa->PRIM_type != T_OPTMGMT_ACK ||
		    toa->MGMT_flags != T_SUCCESS) {
			(void) printf("mibget getmsg(ctl) %d returned %d, "
			    "ctlbuf.len = %d, PRIM_type = %ld\n",
			    j, getcode, ctlbuf.len, toa->PRIM_type);
			if (toa->PRIM_type == T_OPTMGMT_ACK) {
				(void) printf("T_OPTMGMT_ACK: "
				    "MGMT_flags = 0x%lx, req->len = %ld\n",
				    toa->MGMT_flags, req->len);
			}
			errno = ENOMSG;
			break;
		}

		temp = malloc(sizeof (mib_item_t));
		if (temp == NULL) {
			perror("mibget: malloc");
			break;
		}
		if (last_item != NULL)
			last_item->next_item = temp;
		else
			first_item = temp;
		last_item = temp;
		last_item->next_item = NULL;
		last_item->group = req->level;
		last_item->mib_id = req->name;
		last_item->length = req->len;
		last_item->valp = malloc(req->len);
		if (verbose) {
			(void) printf("msg %d:  group = %4ld   mib_id = %5ld   "
			    "length = %ld\n",
			    j, last_item->group, last_item->mib_id,
			    last_item->length);
		}

		databuf.maxlen = last_item->length;
		databuf.buf    = (char *)last_item->valp;
		databuf.len    = 0;
		flags = 0;
		getcode = getmsg(sd, NULL, &databuf, &flags);
		if (getcode < 0) {
			perror("mibget: getmsg (data)");
			break;
		} else if (getcode != 0) {
			(void) printf("mibget getmsg(data) returned %d, "
			    "databuf.maxlen = %d, databuf.len = %d\n",
			    getcode, databuf.maxlen, databuf.len);
			break;
		}
	}

	/*
	 * On error, free all the allocated mib_item_t objects.
	 */
	while (first_item != NULL) {
		last_item = first_item;
		first_item = first_item->next_item;
		free(last_item);
	}
	return (NULL);
}

/*
 * print label security attributes for gateways.
 */
static void
pmsg_secattr(const char *sptr, size_t msglen, const char *labelstr)
{
	rtm_ext_t rtm_ext;
	tsol_rtsecattr_t sp;
	struct rtsa_s *rtsa = &sp.rtsa_attr[0];
	const char *endptr;
	char buf[256];
	int i;

	if (!is_system_labeled())
		return;

	endptr = sptr + msglen;

	for (;;) {
		if (sptr + sizeof (rtm_ext_t) + sizeof (sp) > endptr)
			return;

		bcopy(sptr, &rtm_ext, sizeof (rtm_ext));
		sptr += sizeof (rtm_ext);
		if (rtm_ext.rtmex_type == RTMEX_GATEWAY_SECATTR)
			break;
		sptr += rtm_ext.rtmex_len;
	}

	/* bail if this entry is corrupt or overruns buffer length */
	if (rtm_ext.rtmex_len < sizeof (sp) ||
	    sptr + rtm_ext.rtmex_len > endptr)
		return;

	/* run up just to the end of this extension */
	endptr = sptr + rtm_ext.rtmex_len;

	bcopy(sptr, &sp, sizeof (sp));
	sptr += sizeof (sp);

	if (sptr + (sp.rtsa_cnt - 1) * sizeof (*rtsa) != endptr)
		return;

	for (i = 0; i < sp.rtsa_cnt; i++) {
		if (i > 0) {
			/* first element is part of sp initalized above */
			bcopy(sptr, rtsa, sizeof (*rtsa));
			sptr += sizeof (*rtsa);
		}
		(void) printf("\n%s%s", labelstr, rtsa_to_str(rtsa, buf,
		    sizeof (buf)));
	}
}