/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/tihdr.h>
#include <stropts.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <libintl.h>
#include <locale.h>
#include <unistd.h>
#include <sys/varargs.h>

#include <netinet/in.h>
#include <sys/ethernet.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysmacros.h>
#include <net/if.h>
#include <inet/mib2.h>
#include <inet/ip.h>
#include <net/route.h>
#include <arpa/inet.h>
#include "ncaconf.h"

/* NCA does not support IPv6... */
#ifndef	NCA_MOD_NAME
#define	NCA_MOD_NAME	"nca"
#endif

#ifndef	ARP_MOD_NAME
#define	ARP_MOD_NAME	"arp"
#endif

#define	IF_SEPARATOR	':'

#define	ping_prog	"/usr/sbin/ping"

/* Structure to hold info about each network interface. */
typedef struct nif_s {
	char		name[LIFNAMSIZ+1];
	struct in_addr	local_addr;
	struct in_addr	router_addr;
	uchar_t		router_ether_addr[ETHERADDRL];
} nif_t;

typedef struct mib_item_s {
	struct mib_item_s	*next_item;
	int			group;
	int			mib_id;
	int			length;
	char			*valp;
} mib_item_t;

/* The network interface array. */
static nif_t *nif_list;
/* Number of network interface to process. */
static int num_nif;

/* Interface request to IP. */
static struct lifreq lifr;

/* True if syslog is to be used. */
static boolean_t logging;
/* True if additional debugging messages are printed. */
static boolean_t debug;

/* File descriptor to the routing socket. */
static int rt_fd;

static void logperror(char *);
static void logwarn(char *, ...);
static void logdebug(char *, ...);
static int ip_domux2fd(int *, int *);
static void ip_plink(int, int);
static int find_nca_pos(int);
static int nca_set_nif(int, struct in_addr, uchar_t *);
static void nca_setup(boolean_t *);
static int get_if_ip_addr(void);
static mib_item_t *mibget(int);
static int ire_process(mib2_ipRouteEntry_t *, size_t, boolean_t *);
static int arp_process(mib2_ipNetToMediaEntry_t *, size_t, boolean_t *);
static int get_router_ip_addr(mib_item_t *, boolean_t *);
static int get_router_ether_addr(mib_item_t *, boolean_t *);
static int get_if_info(boolean_t *);
static void daemon_init(void);
static void daemon_work(void);
static void ping_them(void);

/*
 * Print out system error messages, either to syslog or stderr.  Note that
 * syslog() should print out system error messages in the correct language
 * used.  There is no need to use gettext().
 */
static void
logperror(char *str)
{
	if (logging) {
		syslog(LOG_ERR, "%s: %m\n", str);
	} else {
		(void) fprintf(stderr, "ncaconfd: %s: %s\n", str,
		    strerror(errno));
	}
}

/*
 * Print out warning messages.  The caller should use gettext() to have
 * the message printed out in the correct language.
 */
/*PRINTFLIKE1*/
static void
logwarn(char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	if (logging) {
		vsyslog(LOG_WARNING, fmt, ap);
	} else {
		(void) fprintf(stderr, "ncaconfd: ");
		(void) vfprintf(stderr, fmt, ap);
	}
	va_end(ap);
}

/*
 * Print out debugging info.  Note that syslogd(1M) should be configured to
 * take ordinary debug info for it to get this kind of info.
 */
/*PRINTFLIKE1*/
static void
logdebug(char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	if (logging) {
		vsyslog(LOG_WARNING, fmt, ap);
	} else {
		(void) fprintf(stderr, "ncaconfd: ");
		(void) vfprintf(stderr, fmt, ap);
	}
	va_end(ap);
}

/*
 * Helper function for nca_setup().  It gets a fd to the lower IP
 * stream and I_PUNLINK's the lower stream.  It also initializes the
 * global variable lifr.
 *
 * Param:
 *	int *udp_fd: (referenced) fd to /dev/udp (upper IP stream).
 *	int *fd: (referenced) fd to the lower IP stream.
 *
 * Return:
 *	-1 if operation fails, 0 otherwise.
 */
static int
ip_domux2fd(int *udp_fd, int *fd)
{
	int ip_fd;

	if ((ip_fd = open(IP_DEV_NAME, O_RDWR)) < 0) {
		logperror("Cannot open IP");
		return (-1);
	}
	if ((*udp_fd = open(UDP_DEV_NAME, O_RDWR)) < 0) {
		logperror("Cannot open UDP");
		(void) close(ip_fd);
		return (-1);
	}
	if (ioctl(ip_fd, SIOCGLIFMUXID, (caddr_t)&lifr) < 0) {
		logperror("ioctl(SIOCGLIFMUXID) failed");
		(void) close(ip_fd);
		return (-1);
	}
	if (debug) {
		logdebug("ARP_muxid %d IP_muxid %d\n", lifr.lifr_arp_muxid,
		    lifr.lifr_ip_muxid);
	}
	if ((*fd = ioctl(*udp_fd, _I_MUXID2FD, lifr.lifr_ip_muxid)) < 0) {
		logperror("ioctl(_I_MUXID2FD) failed");
		(void) close(ip_fd);
		(void) close(*udp_fd);
		return (-1);
	}
	(void) close(ip_fd);
	return (0);
}

/*
 * Helper function for nca_setup().  It I_PLINK's back the upper and
 * lower IP streams.  Note that this function must be called after
 * ip_domux2fd().  In ip_domux2fd(), the global variable lifr is initialized
 * and ip_plink() needs information in lifr.  So ip_domux2fd() and ip_plink()
 * must be called in pairs.
 *
 * Param:
 *	int udp_fd: fd to /dev/udp (upper IP stream).
 *	int fd: fd to the lower IP stream.
 */
static void
ip_plink(int udp_fd, int fd)
{
	int mux_id;

	if ((mux_id = ioctl(udp_fd, I_PLINK, fd)) < 0) {
		logperror("ioctl(I_PLINK) failed");
		return;
	}
	if (debug > 0) {
		logdebug("New IP_muxid %d\n", mux_id);
	}
	lifr.lifr_ip_muxid = mux_id;
	if (ioctl(udp_fd, SIOCSLIFMUXID, (caddr_t)&lifr) < 0) {
		logperror("ioctl(SIOCSLIFMUXID) failed");
	}
}

#define	FOUND_NCA	-1
#define	FOUND_NONE	-2
/*
 * Find the proper position to insert NCA, which is just below IP.
 *
 * Param:
 *	int fd: fd to the lower IP stream.
 *
 * Return:
 *	If positive, it is the position to insert NCA.
 *	FOUND_NCA: found NCA!  So skip this one for plumbing.  But we
 *		still keep it in the interface list.
 *	FOUND_NONE: could not find IP or encounter other errors.  Remove
 *		this interface from the	list.
 */
static int
find_nca_pos(int fd)
{
	int num_mods;
	int i, pos;
	struct str_list strlist;
	boolean_t found_ip = B_FALSE;
	boolean_t found_nca = B_FALSE;

	if ((num_mods = ioctl(fd, I_LIST, NULL)) < 0) {
		logperror("ioctl(I_LIST) failed");
		return (FOUND_NONE);
	} else {
		strlist.sl_nmods = num_mods;
		strlist.sl_modlist = calloc(num_mods,
		    sizeof (struct str_mlist));
		if (strlist.sl_modlist == NULL) {
			logperror("cannot malloc");
			return (FOUND_NONE);
		} else {
			if (ioctl(fd, I_LIST, (caddr_t)&strlist) < 0) {
				logperror("ioctl(I_LIST) failed");
			} else {
				for (i = 0; i < strlist.sl_nmods; i++) {
					if (strcmp(IP_MOD_NAME,
					    strlist.sl_modlist[i].l_name)
					    == 0) {
						found_ip = B_TRUE;
						/*
						 * NCA should be just below
						 * IP.
						 */
						pos = i + 1;
					} else if (strncmp(NCA_MOD_NAME,
					    strlist.sl_modlist[i].l_name,
					    strlen(NCA_MOD_NAME)) == 0) {
						found_nca = B_TRUE;
					}
				}
			}
			free(strlist.sl_modlist);
		}
	}
	if (found_nca) {
		return (FOUND_NCA);
	} else if (found_ip) {
		if (debug) {
			logdebug("NCA is at position %d in the stream.\n", pos);
		}
		return (pos);
	} else {
		if (debug) {
			logdebug("Cannot find IP??\n");
		}
		return (FOUND_NONE);
	}
}

/*
 * To set the local IP address and default router ethernet address.
 *
 * Param:
 *	int fd: the fd to the lower IP stream.
 *	struct in_addr local_addr: the IP address for this interface.
 *	uchar_t *ether_addr: the ethernet address of the default router for
 *		for this interface.
 *
 * Return:
 *	-1 if the system does not support this NCA ioctl(), 0 otherwise.
 */
static int
nca_set_nif(int fd, struct in_addr local_addr, uchar_t *ether_addr)
{
	struct nca_set_ioctl nca_ioctl;
	struct strioctl strioc;
	int len;
	uchar_t *dst;

	strioc.ic_cmd = NCA_SET_IF;
	strioc.ic_timout = INFTIM;
	strioc.ic_len = sizeof (nca_ioctl);
	strioc.ic_dp = (char *)&nca_ioctl;

	nca_ioctl.local_addr = local_addr.s_addr;
	dst = nca_ioctl.router_ether_addr;
	for (len = ETHERADDRL; len > 0; len--)
		*dst++ = *ether_addr++;
	nca_ioctl.action = ADD_DEF_ROUTE;

	if (ioctl(fd, I_STR, &strioc) < 0) {
		logperror("ioctl(NCA_SET_IF) failed");
		if (errno == EINVAL)
			return (-1);
	}
	return (0);
}

/*
 * To setup the NCA stream.  First insert NCA into the proper position.
 * Then tell NCA the local IP address and default router by using the
 * NCA_SET_IF ioctl.
 *
 * Param:
 *	boolean_t *active: (referenced) B_TRUE if NCA is setup to do active
 *		connection.  If NCA does not support active connection,
 *		in return, active will be set to B_FALSE.
 */
static void
nca_setup(boolean_t *active)
{
	int i;
	int udp_fd;
	int fd;
	struct strmodconf mod;
	/* 128 is enough because interface name can only be LIFNAMSIZ long. */
	char err_buf[128];

	mod.mod_name = NCA_MOD_NAME;
	lifr.lifr_addr.ss_family = AF_INET;
	for (i = 0; i < num_nif; i++) {
		if (debug) {
			logdebug("Plumbing NCA for %s\n", nif_list[i].name);
		}
		/* This interface does not exist according to IP. */
		if (nif_list[i].local_addr.s_addr == 0) {
			continue;
		}
		(void) strlcpy(lifr.lifr_name, nif_list[i].name,
		    sizeof (lifr.lifr_name));

		if (ip_domux2fd(&udp_fd, &fd) < 0) {
			continue;
		}
		if (ioctl(udp_fd, I_PUNLINK, lifr.lifr_ip_muxid) < 0) {
			(void) snprintf(err_buf, sizeof (err_buf),
			    "ioctl(I_PUNLINK) for %s failed", nif_list[i].name);
			logperror(err_buf);
			(void) close(udp_fd);
			(void) close(fd);
			continue;
		}
		if ((mod.pos = find_nca_pos(fd)) < 0) {
			if (mod.pos == FOUND_NCA) {
				if (debug) {
					logdebug("Find NCA in the %s"
					    " stream\n", nif_list[i].name);
				}
				/* Just skip plumbing NCA. */
				goto set_nif;
			}
			if (debug) {
				logdebug("Cannot find pos for %s\n",
				    nif_list[i].name);
			}
			goto clean_up;
		}
		if (ioctl(fd, _I_INSERT, (caddr_t)&mod) < 0) {
			(void) snprintf(err_buf, sizeof (err_buf),
			    "ioctl(_I_INSERT) for %s failed", nif_list[i].name);
			logperror(err_buf);
			goto clean_up;
		}

		/*
		 * Only do the following if NCA is also used to make
		 * outgoing connections, and all necessary info is
		 * there.
		 */
set_nif:
		if (*active && nif_list[i].router_addr.s_addr != 0) {
			if (nca_set_nif(fd, nif_list[i].local_addr,
			    nif_list[i].router_ether_addr) < 0) {
				/*
				 * The system does not support this ioctl()!
				 * Skip all active stack processing but
				 * continue to plumb NCA.
				 */
				logwarn("NCA does not support active stack!");
				*active = B_FALSE;
			}
		}
clean_up:
		ip_plink(udp_fd, fd);
		(void) close(udp_fd);
		(void) close(fd);
	}
}

/*
 * To get IP address of network interface from IP.
 */
static int
get_if_ip_addr(void)
{
	int sock;
	struct lifnum lifn;
	struct lifconf lifc;
	struct lifreq *lifr;
	struct sockaddr_in *sin;
	char *buf;
	int num_lifr;
	int i, j;

	/* NCA only supports IPv4... */
	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		logperror(gettext("Cannot open socket"));
		return (-1);
	}
	lifn.lifn_family = AF_UNSPEC;
	lifn.lifn_flags = 0;
	if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) {
		logperror(gettext("ioctl(SIOCGLIFNUM) failed"));
		(void) close(sock);
		return (-1);
	}
	buf = (char *)calloc(lifn.lifn_count, sizeof (struct lifreq));
	if (buf == NULL) {
		logperror(gettext("calloc() failed"));
		(void) close(sock);
		return (-1);
	}

	lifc.lifc_family = AF_UNSPEC;
	lifc.lifc_flags = 0;
	lifc.lifc_len = lifn.lifn_count * sizeof (struct lifreq);
	lifc.lifc_buf = buf;

	if (ioctl(sock, SIOCGLIFCONF, (char *)&lifc) < 0) {
		/*
		 * NCA is set up after all the interfaces have been
		 * plumbed.  So normally we should not get any error.
		 * Just abort if we encounter an error.
		 */
		logperror(gettext("ioctl(SIOCGLIFCONF) failed"));
		free(buf);
		(void) close(sock);
		return (-1);
	}
	num_lifr = lifc.lifc_len / sizeof (struct lifreq);
	/* Find the interface and copy the local IP address. */
	for (i = 0; i < num_nif; i++) {
		lifr = (struct lifreq *)lifc.lifc_req;
		for (j = num_lifr; j > 0; j--, lifr++) {
			/* Again, NCA only supports IPv4. */
			if (lifr->lifr_addr.ss_family != AF_INET)
				continue;
			if (strncmp(nif_list[i].name, lifr->lifr_name,
			    strlen(nif_list[i].name)) == 0) {
				sin = (struct sockaddr_in *)&lifr->lifr_addr;
				nif_list[i].local_addr = sin->sin_addr;
				if (debug) {
					logdebug("IP address of %s: %s\n",
					    nif_list[i].name,
					    inet_ntoa(sin->sin_addr));
				}
				break;
			}
		}
		if (j == 0) {
			/*
			 * The interface does not exist according to IP!
			 * Log a warning and go on.
			 */
			logwarn(gettext("Network interface %s"
			    " does not exist!\n"), nif_list[i].name);
			/*
			 * Set local_addr to 0 so that nca_setup() will
			 * not do anything for this interface.
			 */
			nif_list[i].local_addr.s_addr = 0;
		}
	}
	free(buf);
	(void) close(sock);
	return (0);
}

/*
 * Get MIB2 info from IP.
 *
 * Param:
 *	int sd: descriptor to IP to send down mib request.
 */
static mib_item_t *
mibget(int sd)
{
	char			buf[1024];
	int			flags;
	int			i, j, getcode;
	struct strbuf		ctlbuf, databuf;
	/* LINTED */
	struct T_optmgmt_req	*tor = (struct T_optmgmt_req *)buf;
	/* LINTED */
	struct T_optmgmt_ack	*toa = (struct T_optmgmt_ack *)buf;
	/* LINTED */
	struct T_error_ack	*tea = (struct T_error_ack *)buf;
	struct opthdr		*req;
	mib_item_t		*first_item = (mib_item_t *)0;
	mib_item_t		*last_item  = (mib_item_t *)0;
	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 = buf;
	ctlbuf.len = tor->OPT_length + tor->OPT_offset;
	flags = 0;
	if (putmsg(sd, &ctlbuf, (struct strbuf *)0, flags) == -1) {
		logperror("mibget: putmsg(ctl) failed");
		goto error_exit;
	}

	/*
	 * 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);
	j = 1;
	for (;;) {
		flags = 0;
		getcode = getmsg(sd, &ctlbuf, (struct strbuf *)0, &flags);
		if (getcode == -1) {
			logperror("mibget getmsg(ctl) failed");
			if (debug) {
				logdebug("#   level   name    len\n");
				i = 0;
				for (last_item = first_item; last_item;
					last_item = last_item->next_item)
					(void) printf("%d  %4d   %5d   %d\n",
					    ++i,
					    last_item->group,
					    last_item->mib_id,
					    last_item->length);
			}
			goto error_exit;
		}
		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 (debug) {
				logdebug("mibget getmsg() %d returned "
				    "EOD (level %ld, name %ld)\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) {
			logwarn("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;
			goto error_exit;
		}

		if (getcode != MOREDATA ||
		    ctlbuf.len < sizeof (struct T_optmgmt_ack) ||
		    toa->PRIM_type != T_OPTMGMT_ACK ||
		    toa->MGMT_flags != T_SUCCESS) {
			logwarn("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) {
				logwarn("T_OPTMGMT_ACK: "
				    "MGMT_flags = 0x%lx, req->len = %ld\n",
				    toa->MGMT_flags, req->len);
			}
			errno = ENOMSG;
			goto error_exit;
		}

		temp = (mib_item_t *)malloc(sizeof (mib_item_t));
		if (!temp) {
			logperror("mibget malloc failed");
			goto error_exit;
		}
		if (last_item)
			last_item->next_item = temp;
		else
			first_item = temp;
		last_item = temp;
		last_item->next_item = (mib_item_t *)0;
		last_item->group = req->level;
		last_item->mib_id = req->name;
		last_item->length = req->len;
		last_item->valp = malloc((int)req->len);

		databuf.maxlen = last_item->length;
		databuf.buf    = last_item->valp;
		databuf.len    = 0;
		flags = 0;
		getcode = getmsg(sd, (struct strbuf *)0, &databuf, &flags);
		if (getcode == -1) {
			logperror("mibget getmsg(data) failed");
			goto error_exit;
		} else if (getcode != 0) {
			logwarn("mibget getmsg(data) returned %d, "
			    "databuf.maxlen = %d, databuf.len = %d\n",
			    getcode, databuf.maxlen, databuf.len);
			goto error_exit;
		}
		j++;
	}

error_exit:;
	while (first_item) {
		last_item = first_item;
		first_item = first_item->next_item;
		free(last_item);
	}
	return (first_item);
}

/*
 * Examine the IPv4 routing table for default routers.  For each interface,
 * find its default router.
 *
 * Param:
 *	mib2_ipRouteEntry_t *buf: the mib info buffer.
 *	size_t len: length of buffer.
 *	boolean_t *changed (referenced): set to B_TRUE if there is a change
 *		in router info.
 *
 * Return:
 *	number of default router found.
 */
static int
ire_process(mib2_ipRouteEntry_t *buf, size_t len, boolean_t *changed)
{
	mib2_ipRouteEntry_t 	*rp;
	mib2_ipRouteEntry_t 	*rp1;
	mib2_ipRouteEntry_t 	*rp2;
	struct	in_addr		nexthop_v4;
	mib2_ipRouteEntry_t	*endp;
	char			ifname[LIFNAMSIZ + 1];
	char			*cp;
	int			i;
	int			ifname_len;
	boolean_t		found;
	int			num_found = 0;

	if (len == 0)
		return (0);
	endp = buf + (len / sizeof (mib2_ipRouteEntry_t));

	for (i = 0; i < num_nif; i++) {
		/*
		 * Loop thru the routing table entries. Process any
		 * IRE_DEFAULT ire.  Ignore the others.  For each such
		 * ire, get the nexthop gateway address.
		 */
		found = B_FALSE;
		for (rp = buf; rp < endp; rp++) {
			/*
			 * NCA is only interested in default routes associated
			 * with an interface.
			 */
			if (!(rp->ipRouteInfo.re_ire_type & IRE_DEFAULT)) {
				continue;
			}
			/*  Get the nexthop address. */
			nexthop_v4.s_addr = rp->ipRouteNextHop;

			/*
			 * Right now, not all IREs have the interface name
			 * it is associated with.
			 */
			if (rp->ipRouteIfIndex.o_length == 0) {
				/*
				 * We don't have the outgoing interface in
				 * this case.  Get the nexthop address. Then
				 * determine the outgoing interface, by
				 * examining all interface IREs, and
				 * picking the match.
				 */
				for (rp1 = buf; rp1 < endp; rp1++) {

				if (!(rp1->ipRouteInfo.re_ire_type &
				    IRE_INTERFACE)) {
					continue;
				}

				/*
				 * Determine the interface IRE that
				 * matches the nexthop. i.e.
				 * (IRE addr & IRE mask) ==
				 * (nexthop & IRE mask)
				 */
				if ((rp1->ipRouteDest & rp1->ipRouteMask) ==
				    (nexthop_v4.s_addr & rp1->ipRouteMask)) {
					/*
					 * We found the interface to go to
					 * the default router.  Check the
					 * interface name.
					 */
					/* Can this be possible?? */
					if (rp1->ipRouteIfIndex.o_length == 0)
						continue;
					rp2 = rp1;
					break;
				}

				} /* End inner for loop. */
			} else {
				rp2 = rp;
			}

			ifname_len = MIN(rp2->ipRouteIfIndex.o_length,
			    sizeof (ifname) - 1);
			(void) memcpy(ifname, rp2->ipRouteIfIndex.o_bytes,
			    ifname_len);
			ifname[ifname_len] = '\0';
			if (ifname[0] == '\0')
				continue;
			cp = strchr(ifname, IF_SEPARATOR);
			if (cp != NULL)
				*cp = '\0';

			/* We are sure both are NULL terminated. */
			if (strcmp(nif_list[i].name, ifname) == 0) {
				/* No change, do not do anything. */
				if (nexthop_v4.s_addr ==
				    nif_list[i].router_addr.s_addr) {
					found = B_TRUE;
					break;
				}
				nif_list[i].router_addr.s_addr =
				    nexthop_v4.s_addr;
				if (debug) {
					logdebug("Get default"
					    " router for %s: %s\n", ifname,
					    inet_ntoa(nexthop_v4));
				}
				found = B_TRUE;
				*changed = B_TRUE;
				break;
			}

		}
		if (!found) {
			/*
			 * The interface does not have a default router.
			 * Log a warning and go on.
			 */
			logwarn(gettext("Network interface %s"
			    " does not have a default router.\n"),
			    nif_list[i].name);
			/*
			 * Set router_addr to 0 so that we will
			 * not do anything for this interface.
			 */
			nif_list[i].router_addr.s_addr = 0;
		} else {
			num_found++;
		}
	}
	return (num_found);
}

/*
 * Examine the ARP table to find ethernet address for default routers.
 *
 * Param:
 *	mib2_ipNetToMdeiaEntry_t *buf: the mib info buffer.
 *	size_t len: length of buffer.
 *	boolean_t *changed (referenced): set to B_TRUE if there is any change
 *		in ethernet address for any default router.
 *
 * Return:
 *	number of ethernet address found.
 */
static int
arp_process(mib2_ipNetToMediaEntry_t *buf, size_t len, boolean_t *changed)
{
	mib2_ipNetToMediaEntry_t 	*rp;
	mib2_ipNetToMediaEntry_t	*endp;
	int				i;
	boolean_t			found;
	int				num_found = 0;
	uchar_t				*src, *dst;

	if (len == 0)
		return (0);
	endp = buf + (len / sizeof (mib2_ipNetToMediaEntry_t));

	for (i = 0; i < num_nif; i++) {
		/*
		 * Loop thru the arp table entries and find the ethernet
		 * address of those default routers.
		 */
		if (nif_list[i].router_addr.s_addr == 0)
			continue;
		found = B_FALSE;
		for (rp = buf; rp < endp; rp++) {
			if (rp->ipNetToMediaNetAddress ==
			    nif_list[i].router_addr.s_addr) {
				/*
				 * Sanity check.  Make sure that this
				 * default router is only reachable thru this
				 * interface.
				 */
				if (rp->ipNetToMediaIfIndex.o_length !=
				    strlen(nif_list[i].name) ||
				    strncmp(rp->ipNetToMediaIfIndex.o_bytes,
					nif_list[i].name,
					rp->ipNetToMediaIfIndex.o_length) !=
				    0) {
					break;
				}
				/* No change, do not do anything. */
				if (bcmp(nif_list[i].router_ether_addr,
				    rp->ipNetToMediaPhysAddress.o_bytes,
				    ETHERADDRL) == 0) {
					found = B_TRUE;
					continue;
				}
				dst = nif_list[i].router_ether_addr;
				src = (uchar_t *)
				    rp->ipNetToMediaPhysAddress.o_bytes;
				for (len = ETHERADDRL; len > 0; len--)
					*dst++ = *src++;
				if (debug) {
					int j;
					uchar_t *cp;
					char err_buf[128];

					(void) snprintf(err_buf,
					    sizeof (err_buf),
					    "Get address for %s: ",
					    inet_ntoa(nif_list[i].router_addr));
					cp = (uchar_t *)
					    nif_list[i].router_ether_addr;
					for (j = 0; j < ETHERADDRL; j++) {
						(void) sprintf(err_buf +
						    strlen(err_buf),
						    "%02x:", 0xff & cp[j]);
					}
					(void) sprintf(err_buf +
					    strlen(err_buf) - 1, "\n");
					logdebug(err_buf);
				}
				found = B_TRUE;
				*changed = B_TRUE;
			}
		}
		if (!found) {
			logwarn("Cannot reach %s using %s\n",
			    inet_ntoa(nif_list[i].router_addr),
			    nif_list[i].name);
			/* Clear this default router. */
			nif_list[i].router_addr.s_addr = 0;
		} else {
			num_found++;
		}
	}
	return (num_found);
}

/*
 * Get IP address of default routers for each interface.
 *
 * Param:
 *	mib_item_t *item: the mib info buffer.
 *	boolean_t *changed (referenced): set to B_TRUE if there is any change
 *		in router info.
 *
 * Return:
 *	-1 if there is no router found, 0 otherwise.
 */
static int
get_router_ip_addr(mib_item_t *item, boolean_t *changed)
{
	int found = 0;

	for (; item != NULL; item = item->next_item) {
		/* NCA does not support IPv6... */
		if (!(item->group == MIB2_IP && item->mib_id == MIB2_IP_ROUTE))
			continue;
		/* LINTED */
		found += ire_process((mib2_ipRouteEntry_t *)item->valp,
		    item->length, changed);
	}
	if (found == 0)
		return (-1);
	else
		return (0);
}

/*
 * Get Ethernet address for each default router from ARP.
 *
 * Param:
 *	mib_item_t *item: the mib info buffer.
 *	boolean_t *changed (referenced): set to B_TRUE if there is any change
 *		in ethernet address of router.
 *
 * Return:
 *	-1 if there is no ethernet address found, 0 otherwise.
 */
static int
get_router_ether_addr(mib_item_t *item, boolean_t *changed)
{
	int found = 0;

	for (; item != NULL; item = item->next_item) {
		/* NCA does not support IPv6... */
		if (!(item->group == MIB2_IP && item->mib_id == MIB2_IP_MEDIA))
			continue;
		/* LINTED */
		found += arp_process((mib2_ipNetToMediaEntry_t *)item->valp,
		    item->length, changed);
	}
	if (found == 0)
		return (-1);
	else
		return (0);
}

/*
 * Ping all default routers.  It just uses system(3F) to call
 * ping(1M) to do the job...
 */
static void
ping_them(void)
{
	int i;
	char ping_cmd[128];

	for (i = 0; i < num_nif; i++) {
		if (nif_list[i].router_addr.s_addr != 0) {
			(void) snprintf(ping_cmd, sizeof (ping_cmd),
			    "%s %s > /dev/null 2>&1",
			    ping_prog,
			    inet_ntoa(nif_list[i].router_addr));
			(void) system(ping_cmd);
		}
	}
}

/*
 * To get default router info (both IP address and ethernet address) for
 * each configured interface from IP.
 *
 * Param:
 *	boolean_t *changed (referenced): set to B_TRUE if there is any change
 *		of info.
 *
 * Return:
 *	-1 if there is any error, 0 if everything is fine.
 */
static int
get_if_info(boolean_t *changed)
{
	int mib_fd;
	mib_item_t *item;
	boolean_t ip_changed = B_FALSE;
	boolean_t ether_changed = B_FALSE;

	if ((mib_fd = open(IP_DEV_NAME, O_RDWR)) < 0) {
		logperror("cannot open ip to get router info");
		return (-1);
	}
	if (ioctl(mib_fd, I_PUSH, ARP_MOD_NAME) == -1) {
		logperror("cannot push arp");
		goto err;
	}

	if ((item = mibget(mib_fd)) == NULL) {
		goto err;
	}

	if (get_router_ip_addr(item, &ip_changed) < 0) {
		goto err;
	}
	/*
	 * Ping every routers to make sure that ARP has all their ethernet
	 * addresses.
	 */
	ping_them();
	/*
	 * If the router IP address is not changed, its ethernet address
	 * should not be changed.  But just in case there is some IP
	 * failover going on...
	 */
	if (get_router_ether_addr(item, &ether_changed) < 0) {
		goto err;
	}
	(void) close(mib_fd);
	*changed = ip_changed || ether_changed;
	return (0);
err:
	(void) close(mib_fd);
	return (-1);
}

/*
 * To remove the default router from an interface.
 *
 * Param:
 *	struct in_addr gw_addr: the IP address of the default router to be
 *	removed.
 */
static void
nca_del_nif(struct in_addr gw_addr)
{
	struct nca_set_ioctl nca_ioctl;
	struct strioctl strioc;
	int i;
	int udp_fd, fd;

	/* Search for the interface for this router. */
	for (i = 0; i < num_nif; i++) {
		if (nif_list[i].router_addr.s_addr == gw_addr.s_addr)
			break;
	}
	if (i == num_nif)
		return;

	if (ip_domux2fd(&udp_fd, &fd) < 0) {
		logwarn(gettext("Removing interface %s from the"
		    " configuration list.\n"), nif_list[i].name);
		nif_list[i].name[0] = 0;
		return;
	}
	if (ioctl(udp_fd, I_PUNLINK, lifr.lifr_ip_muxid) < 0) {
		logwarn(gettext("Removing interface %s from the"
		    " configuration list.\n"), nif_list[i].name);
		nif_list[i].name[0] = 0;
		(void) close(udp_fd);
		(void) close(fd);
		return;
	}

	strioc.ic_cmd = NCA_SET_IF;
	strioc.ic_timout = INFTIM;
	strioc.ic_len = sizeof (nca_ioctl);
	strioc.ic_dp = (char *)&nca_ioctl;

	nca_ioctl.local_addr = 0;
	(void) memset(nca_ioctl.router_ether_addr, 0, ETHERADDRL);
	nca_ioctl.action = DEL_DEF_ROUTE;

	if (ioctl(fd, I_STR, &strioc) < 0) {
		logperror("ioctl(NCA_SET_IF) failed");
	}
	ip_plink(udp_fd, fd);
	(void) close(udp_fd);
	(void) close(fd);

	/* Clear the fields for this interface. */
	nif_list[i].router_addr.s_addr = 0;
	(void) memset(nif_list[i].router_ether_addr, 0, ETHERADDRL);
}

/*
 * Wait for any changes in the routing table.  If there are changes to
 * IP address or router ethernet address, send down the info to NCA.
 */
static void
daemon_work(void)
{
	int n;
	int i;
	int udp_fd;
	int fd;
	int64_t msg[2048/8];
	struct rt_msghdr *rtm;
	boolean_t changed;
	struct sockaddr_in *sin;
	struct in_addr gw_addr;
	uchar_t *cp;

	/* Loop forever waiting for any routing changes. */
	for (;;) {
		if (debug) {
			logdebug("Waiting to read routing info...\n");
		}
		n = read(rt_fd, msg, sizeof (msg));
		/* Don't die...  Reinitialize socket and listen again. */
		if (n <= 0) {
			if (debug) {
				logdebug("Routing socket read error.\n");
			}
			(void) close(rt_fd);
			rt_fd = socket(PF_ROUTE, SOCK_RAW, AF_INET);
			i = 0;
			while (rt_fd < 0) {
				if (i++ == 0) {
					logperror(gettext("cannot reinitialize"
					    " routing socket"));
				} else if (i > 5) {
					logwarn(gettext("Give up on trying to"
					    " reinitializing routing"
					    " socket\n"));
					exit(1);
				}
				/* May be a transient error... */
				(void) sleep(10);
				rt_fd = socket(PF_ROUTE, SOCK_RAW, AF_INET);
			}
		} else {
			rtm = (struct rt_msghdr *)msg;
			if (rtm->rtm_version != RTM_VERSION) {
				logwarn(gettext("Do non understand routing"
				    " socket info.\n"));
				continue;
			}
			if (debug) {
				logdebug("Get routing info.\n");
			}
			switch (rtm->rtm_type) {
			case RTM_DELETE:
			case RTM_OLDDEL:
				sin = (struct sockaddr_in *)(rtm + 1);
				cp = (uchar_t *)sin;
				/* Only handle default route deletion. */
				if ((rtm->rtm_addrs & RTA_DST) &&
				    (sin->sin_addr.s_addr == 0)) {
					if (!(rtm->rtm_addrs & RTA_GATEWAY)) {
						break;
					}
					cp += sizeof (struct sockaddr_in);
					/* LINTED */
					sin = (struct sockaddr_in *)cp;
					gw_addr = sin->sin_addr;
					if (debug) {
						logdebug("Get default route "
						    "removal notice: gw %s\n",
						    inet_ntoa(gw_addr));
					}
					nca_del_nif(gw_addr);
				}
				break;
			case RTM_ADD:
			case RTM_OLDADD:
			case RTM_CHANGE:
				changed = B_FALSE;
				if (get_if_info(&changed) < 0) {
					/* May be a transient error... */
					(void) sleep(10);
					break;
				}
				/* Nothing is changed, do nothing. */
				if (!changed) {
					if (debug) {
						logdebug("Get route change "
						    "notice, but nothing is "
						    "changed for us!");
					}
					break;
				}
				lifr.lifr_addr.ss_family = AF_INET;
				for (i = 0; i < num_nif; i++) {
					int ret;

					/*
					 * If name is NULL, it means that
					 * we have encontered some problems
					 * when configurating the interface.
					 * So we remove it from the list.
					 */
					if (nif_list[i].name[0] == 0 ||
					    nif_list[i].local_addr.s_addr == 0)
						continue;
					(void) strlcpy(lifr.lifr_name,
					    nif_list[i].name,
					    sizeof (lifr.lifr_name));
					if (ip_domux2fd(&udp_fd, &fd) < 0) {
						logwarn(gettext("Removing"
						    " interface %s from the"
						    " configuration list.\n"),
						    nif_list[i].name);
						nif_list[i].name[0] = 0;
						continue;
					}
					if (ioctl(udp_fd, I_PUNLINK,
					    lifr.lifr_ip_muxid) < 0) {
						logwarn(gettext("Removing"
						    " interface %s from the"
						    " configuration list.\n"),
						    nif_list[i].name);
						nif_list[i].name[0] = 0;
						(void) close(udp_fd);
						(void) close(fd);
						continue;
					}
					if (debug) {
						logdebug("Configuring"
						    " %s\n", nif_list[i].name);
					}
					ret = nca_set_nif(fd,
					    nif_list[i].local_addr,
					    nif_list[i].router_ether_addr);
					ip_plink(udp_fd, fd);
					if (ret < 0) {
						/*
						 * This should not be possible
						 * since if NCA does not
						 * support the ioctl, the
						 * active flag should be
						 * cleared already and this
						 * function should not have
						 * been called at all!
						 */
						logwarn("Daemon dies\n");
						exit(1);
					}
					(void) close(udp_fd);
					(void) close(fd);
				}
				break;
			default:
				continue;
			}
		}
	}
}

/*
 * Make us a daemon.
 */
static void
daemon_init(void)
{
	pid_t pid;

	if ((pid = fork()) == -1) {
		/* Write directly to terminal, instead of syslog. */
		(void) fprintf(stderr, gettext("ncaconfd: cannot fork: %s\n"),
		    strerror(errno));
		exit(1);
	}
	if (pid != 0)
		exit(0);
	(void) setsid();
	/* Fork again so that we will never get a controlling terminal. */
	if ((pid = fork()) == -1) {
		/* Write directly to terminal, instead of syslog. */
		(void) fprintf(stderr, gettext("ncaconfd: cannot fork: %s\n"),
		    strerror(errno));
		exit(1);
	}
	if (pid != 0)
		exit(0);
	(void) chdir("/");
	(void) umask(0);
	(void) fclose(stdin);
	(void) fclose(stdout);
	(void) fclose(stderr);
}

int
main(int argc, char **argv)
{
	int i, j;
	int c;
	boolean_t active = B_FALSE;
	boolean_t as_daemon = B_TRUE;

	if (argc == 1) {
		(void) fprintf(stderr, gettext("Usage: %s [-al]"
		    " [interface1 interface2 ...]\n"), argv[0]);
		return (1);
	}

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

	while ((c = getopt(argc, argv, "adcl")) != EOF) {
		switch (c) {
		case 'a':
			active = B_TRUE;
			break;
		case 'd':
			debug = B_TRUE;
			break;
		case 'c':
			/* Don't run as daemon. */
			as_daemon = B_FALSE;
			break;
		case 'l':
			logging = B_TRUE;
			break;
		default:
			/* -d and -c are "undocumented" options. */
			(void) fprintf(stderr, gettext("Usage: %s [-al]"
			    " [interface1 interface2 ...]\n"), argv[0]);
			return (1);
		}
	}
	num_nif = argc - optind;
	if (num_nif == 0) {
		/* No network interface to proces... */
		(void) fprintf(stderr, gettext("Usage: %s [-al]"
		    " [interface1 interface2 ...]\n"), argv[0]);
		return (0);
	}
	nif_list = calloc(num_nif, sizeof (nif_t));
	if (nif_list == NULL) {
		(void) fprintf(stderr, gettext("ncaconfd: Cannot malloc: %s\n"),
		    strerror(errno));
		return (1);
	}
	for (i = 0, j = optind; i < num_nif; i++, j++) {
		(void) strlcpy(nif_list[i].name, argv[j], LIFNAMSIZ+1);
	}

	/* Get IP address info for all the intefaces. */
	if (get_if_ip_addr() < 0) {
		if (debug) {
			(void) fprintf(stderr, "ncaconfd: Cannot get IP"
			    " addresses for interfaces.\n");
		}
		return (1);
	}
	if (logging)
		openlog("ncaconfd", LOG_PID, LOG_DAEMON);
	/* No need to run as daemon if NCA is not making active connections. */
	if (active && as_daemon)
		daemon_init();
	if (active) {
		boolean_t changed;

		/* NCA does not support IPv6... */
		if ((rt_fd = socket(PF_ROUTE, SOCK_RAW, AF_INET)) < 0) {
			logperror("Cannot open routing socket");
			return (1);
		}
		/*
		 * At boot up time, the default router may not have been
		 * found.  So ignore the error and check later.
		 */
		if (get_if_info(&changed) < 0) {
			if (debug) {
				(void) logwarn("Cannot get"
				    " information from network interface.\n");
			}
		}
	}
	/* Do the set up as daemon (if we are) to save time at boot up... */
	nca_setup(&active);
	if (active)
		daemon_work();
	return (0);
}