/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2015 Joyent, Inc.  All rights reserved.
 */

/*
 * ndp - display and manipulate Neighbor Cache Entries from NDP
 */

#include <stdio.h>
#include <stdarg.h>
#include <signal.h>
#include <time.h>
#include <err.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <wait.h>
#include <sys/mac.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <netdb.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <inet/ip.h>
#include <net/if_dl.h>
#include <net/route.h>

typedef	struct	sockaddr_in6	sin6_t;

#define	BUF_SIZE 2048
typedef struct rtmsg_pkt {
	struct	rt_msghdr m_rtm;
	char	m_space[BUF_SIZE];
} rtmsg_pkt_t;

enum ndp_action {
	NDP_A_DEFAULT,
	NDP_A_GET,		/* Show a single NDP entry */
	NDP_A_GET_ALL,		/* Show NDP entries */
	NDP_A_GET_FOREVER,	/* Repeatedly show entries */
	NDP_A_DELETE,		/* Delete an NDP entry */
	NDP_A_SET_NCE,		/* Set NDP entry */
	NDP_A_SET_FILE		/* Read in & set NDP entries */
};

typedef	int	(ndp_addr_f)(int, struct lifreq *, void *);
typedef	void	(ndp_void_f)(void);

static	void	ndp_usage(const char *, ...);
static	void	ndp_fatal(const char *, ...);
static	void	ndp_badflag(enum ndp_action);
static	void	ndp_missingarg(char);

static	void	ndp_run_in_child(ndp_void_f *);
static	void	ndp_do_run(int);
static	void	ndp_setup_handler(sigset_t *);
static	void	ndp_start_timer(time_t period);
static	void	ndp_run_periodically(time_t, ndp_void_f *);

static	int	ndp_salen(const struct sockaddr *sa);
static	int	ndp_extract_sockaddrs(struct rt_msghdr *, struct sockaddr **,
		    struct sockaddr **, struct sockaddr **, struct sockaddr **,
		    struct sockaddr_dl **);
static	int	ndp_rtmsg_get(int, rtmsg_pkt_t *, struct sockaddr *);
static	int	ndp_find_interface(int, struct sockaddr *, char *, int);

static	int	ndp_initialize_lifreq(int, struct lifreq *, struct sockaddr *);
static	int	ndp_host_enumerate(char *, ndp_addr_f *, void *);

static	int	ndp_display(struct lifreq *);
static	int	ndp_display_missing(struct lifreq *);
static	void	ndp_lifr2ip(struct lifreq *, char *, int);

static	int	ndp_get(int, struct lifreq *, void *);
static	void	ndp_get_all(void);
static	int	ndp_delete(int, struct lifreq *, void *);
static	int	ndp_set(int, struct lifreq *, void *);
static	int	ndp_set_nce(char *, char *, char *[], int);
static	int	ndp_set_file(char *);

static	char		*ndp_iface = NULL;
static	char		*netstat_path = "/usr/bin/netstat";
static	pid_t		ndp_pid;
static	boolean_t	ndp_noresolve = B_FALSE; /* Don't lookup addresses */
static	boolean_t	ndp_run = B_TRUE;

#define	MAX_ATTEMPTS 5
#define	MAX_OPTS 5
#define	WORDSEPS " \t\r\n"

/*
 * Macros borrowed from route(8) for working with PF_ROUTE messages
 */
#define	RT_ADVANCE(x, n) ((x) += ndp_salen(n))
#define	RT_NEXTADDR(cp, w, u) \
	l = ndp_salen(u); \
	(void) memmove(cp, u, l); \
	cp += l;

/*
 * Print an error to stderr and then exit non-zero.
 */
static void
ndp_fatal(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	vwarnx(format, ap);
	va_end(ap);
	exit(EXIT_FAILURE);
}

/*
 * Print out the command usage to stderr, along with any reason why it's being
 * printed, and then exit non-zero.
 */
static void
ndp_usage(const char *reason, ...)
{
	va_list ap;
	const char *ndp_progname = getprogname();

	if (reason != NULL) {
		va_start(ap, reason);
		(void) fprintf(stderr, "%s: ", ndp_progname);
		(void) vfprintf(stderr, reason, ap);
		(void) fprintf(stderr, "\n");
		va_end(ap);
	}

	(void) fprintf(stderr,
	    "Usage: %s [-n] [-i iface] hostname\n"
	    "       %s [-n] [-i iface] -s nodeaddr etheraddr [temp] [proxy]\n"
	    "       %s [-n] [-i iface] -d nodeaddr\n"
	    "       %s [-n] [-i iface] -f filename\n"
	    "       %s [-n] -a\n"
	    "       %s [-n] -A period\n",
	    ndp_progname, ndp_progname, ndp_progname,
	    ndp_progname, ndp_progname, ndp_progname);
	exit(EXIT_FAILURE);
}

static void
ndp_badflag(enum ndp_action action)
{
	switch (action) {
	case NDP_A_DEFAULT:
	case NDP_A_GET:
		ndp_usage("Already going to print an entry, "
		    "but extra -%c given", optopt);
		break;
	case NDP_A_GET_ALL:
		ndp_usage("Already going to print all entries (-a), "
		    "but extra -%c given", optopt);
		break;
	case NDP_A_GET_FOREVER:
		ndp_usage("Already going to repeatedly print all entries (-A), "
		    "but extra -%c given", optopt);
		break;
	case NDP_A_DELETE:
		ndp_usage("Already going to delete an entry (-d), "
		    "but extra -%c given", optopt);
		break;
	case NDP_A_SET_NCE:
		ndp_usage("Already going to set an entry (-s), "
		    "but extra -%c given", optopt);
		break;
	case NDP_A_SET_FILE:
		ndp_usage("Already going to set from file (-f), "
		    "but extra -%c given", optopt);
		break;
	}
}

static void
ndp_missingarg(char flag)
{
	switch (flag) {
	case 'A':
		ndp_usage("Missing time period after -%c", flag);
		break;
	case 'd':
		ndp_usage("Missing node name after -%c", flag);
		break;
	case 'f':
		ndp_usage("Missing filename after -%c", flag);
		break;
	case 's':
		ndp_usage("Missing node name after -%c", flag);
		break;
	case 'i':
		ndp_usage("Missing interface name after -%c", flag);
		break;
	default:
		ndp_usage("Missing option argument after -%c", flag);
		break;
	}
}

/*
 * Run a function that's going to exec in a child process, and don't return
 * until it exits.
 */
static void
ndp_run_in_child(ndp_void_f *func)
{
	pid_t child_pid;
	int childstat = 0, status = 0;

	child_pid = fork();
	if (child_pid == (pid_t)-1) {
		ndp_fatal("Unable to fork: %s", strerror(errno));
	} else if (child_pid == (pid_t)0) {
		func();
		exit(EXIT_FAILURE);
	}

	while (waitpid(child_pid, &childstat, 0) == -1) {
		if (errno == EINTR)
			continue;

		ndp_fatal("Failed to wait on child: %s", strerror(errno));
	}

	status = WEXITSTATUS(childstat);
	if (status != 0) {
		ndp_fatal("Child process exited with %d", status);
	}
}

/*
 * SIGALRM handler to schedule a run.
 */
static void
ndp_do_run(int signal __unused)
{
	ndp_run = B_TRUE;
}


/*
 * Prepare signal masks, and install the SIGALRM handler. Return old signal
 * masks through the first argument.
 */
static void
ndp_setup_handler(sigset_t *oset)
{
	struct sigaction sa;

	/*
	 * Mask off SIGALRM so we only trigger the handler when we're ready
	 * using sigsuspend(2), in case the child process takes longer to
	 * run than the alarm interval.
	 */
	if (sigprocmask(0, NULL, oset) != 0) {
		ndp_fatal("Unable to set signal mask: %s", strerror(errno));
	}

	if (sighold(SIGALRM) != 0) {
		ndp_fatal("Unable to add SIGALRM to signal mask: %s",
		    strerror(errno));
	}

	sa.sa_flags = 0;
	sa.sa_handler = ndp_do_run;

	if (sigemptyset(&sa.sa_mask) != 0) {
		ndp_fatal("Unable to prepare empty signal set: %s",
		    strerror(errno));
	}

	if (sigaction(SIGALRM, &sa, NULL) != 0) {
		ndp_fatal("Unable to install timer handler: %s",
		    strerror(errno));
	}
}

/*
 * Start the printing timer.
 */
static void
ndp_start_timer(time_t period)
{
	timer_t timer;
	struct itimerspec interval;
	interval.it_value.tv_sec  = interval.it_interval.tv_sec  = period;
	interval.it_value.tv_nsec = interval.it_interval.tv_nsec = 0;

	if (timer_create(CLOCK_REALTIME, NULL, &timer) != 0) {
		ndp_fatal("Unable to create timer: %s", strerror(errno));
	}

	if (timer_settime(timer, 0, &interval, NULL) != 0) {
		ndp_fatal("Unable to set time on timer: %s", strerror(errno));
	}
}


/*
 * Run a given function forever periodically in a child process.
 */
static void
ndp_run_periodically(time_t period, ndp_void_f *func)
{
	sigset_t oset;

	ndp_setup_handler(&oset);
	ndp_start_timer(period);

	do {
		if (ndp_run) {
			ndp_run = B_FALSE;
			ndp_run_in_child(func);
		}
		(void) sigsuspend(&oset);
	} while (errno == EINTR);

	/*
	 * Only an EFAULT should get us here. Abort so we get a core dump.
	 */
	warnx("Failure while waiting on timer: %s", strerror(errno));
	abort();
}

/*
 * Given an address, return its size.
 */
static int
ndp_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:
		warnx("Unrecognized sockaddr with address family %d!",
		    sa->sa_family);
		abort();
	}
	/*NOTREACHED*/
}

/*
 * Extract all socket addresses from a routing message, and return them
 * through the pointers given as arguments to ndp_extract_sockaddrs. None
 * of the pointers should be null.
 */
static int
ndp_extract_sockaddrs(struct rt_msghdr *rtm, struct sockaddr **dst,
    struct sockaddr **gate, struct sockaddr **mask, struct sockaddr **src,
    struct sockaddr_dl **ifp)
{
	struct sockaddr *sa;
	char *cp;
	int i;

	if (rtm->rtm_version != RTM_VERSION) {
		warnx("Routing message version %d not understood",
		    rtm->rtm_version);
		return (-1);
	}

	if (rtm->rtm_errno != 0)  {
		warnx("Routing message couldn't be processed: %s",
		    strerror(rtm->rtm_errno));
		return (-1);
	}

	cp = ((char *)(rtm + 1));
	if (rtm->rtm_addrs != 0) {
		for (i = 1; i != 0; i <<= 1) {
			if ((i & rtm->rtm_addrs) == 0)
				continue;

			/*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;
			}
			RT_ADVANCE(cp, sa);
		}
	}

	return (0);
}

/*
 * Given an IPv6 address, use routing information to look up
 * the destination and interface it would pass through.
 */
static int
ndp_rtmsg_get(int fd, rtmsg_pkt_t *msg, struct sockaddr *sin6p)
{
	static int seq = 0;
	struct sockaddr_dl sdl;
	int mlen, l;
	char ipaddr[INET6_ADDRSTRLEN];
	char *cp = msg->m_space;
	struct	rt_msghdr *m_rtm = &msg->m_rtm;

	bzero(msg, sizeof (rtmsg_pkt_t));
	bzero(&sdl, sizeof (struct sockaddr_dl));

	m_rtm->rtm_type = RTM_GET;
	m_rtm->rtm_version = RTM_VERSION;
	m_rtm->rtm_seq = ++seq;
	m_rtm->rtm_addrs = RTA_DST | RTA_IFP;
	m_rtm->rtm_msglen = sizeof (rtmsg_pkt_t);

	/* Place the address we're looking up after the header */
	RT_NEXTADDR(cp, RTA_DST, sin6p);

	/* Load an empty link-level address, so we get an interface back */
	sdl.sdl_family = AF_LINK;
	RT_NEXTADDR(cp, RTA_IFP, (struct sockaddr *)&sdl);

	m_rtm->rtm_msglen = cp - (char *)msg;

	if ((mlen = write(fd, (char *)msg, m_rtm->rtm_msglen)) < 0) {
		if (errno == ESRCH) {
			/*LINTED*/
			if (inet_ntop(AF_INET6, &((sin6_t *)sin6p)->sin6_addr,
			    ipaddr, sizeof (ipaddr)) == NULL) {
				(void) snprintf(ipaddr, sizeof (ipaddr),
				    "(failed to format IP)");
			};
			warnx("An appropriate interface for the address %s "
			    "is not in the routing table; use -i to force an "
			    "interface", ipaddr);
			return (-1);
		} else {
			warnx("Failed to send routing message: %s",
			    strerror(errno));
			return (-1);
		}
	} else if (mlen < (int)m_rtm->rtm_msglen) {
		warnx("Failed to write all bytes to routing socket");
		return (-1);
	}

	/*
	 * Keep reading routing messages until we find the response to the one
	 * we just sent. Note that we depend on the sequence number being unique
	 * to the running program.
	 */
	do {
		mlen = read(fd, (char *)msg, sizeof (rtmsg_pkt_t));
	} while (mlen > 0 &&
	    (m_rtm->rtm_seq != seq || m_rtm->rtm_pid != ndp_pid));
	if (mlen < 0) {
		warnx("Failed to read from routing socket: %s",
		    strerror(errno));
		return (-1);
	}

	return (0);
}

/*
 * Find the interface that the IPv6 address would be routed through, and store
 * the name of the interface in the buffer passed in.
 */
static int
ndp_find_interface(int fd, struct sockaddr *sin6p, char *buf, int buflen)
{
	struct sockaddr *dst = NULL, *gate = NULL, *mask = NULL, *src = NULL;
	struct sockaddr_dl *ifp = NULL;
	rtmsg_pkt_t msg;

	if (ndp_rtmsg_get(fd, &msg, sin6p) != 0) {
		return (-1);
	}

	if (ndp_extract_sockaddrs(&msg.m_rtm, &dst, &gate,
	    &mask, &src, &ifp) != 0) {
		return (-1);
	}

	if (ifp == NULL) {
		warnx("Unable to find appropriate interface for address");
		return (-1);
	} else {
		if (ifp->sdl_nlen >= buflen) {
			warnx("The interface name \"%.*s\" is too big for the "
			    "available buffer", ifp->sdl_nlen, ifp->sdl_data);
			return (-1);
		} else {
			(void) snprintf(buf, buflen, "%.*s", ifp->sdl_nlen,
			    ifp->sdl_data);
		}
	}

	return (0);
}

/*
 * Zero out a lifreq struct for a SIOCLIF*ND ioctl, set the address, and fetch
 * the appropriate interface using the given routing socket.
 */
static int
ndp_initialize_lifreq(int route, struct lifreq *lifrp, struct sockaddr *sap)
{
	struct sockaddr_storage *lnr_addr;
	/* LINTED E_BAD_PTR_CAST_ALIGN */
	struct sockaddr_in6 *sin6p = (sin6_t *)sap;
	char *lifr_name = lifrp->lifr_name;

	bzero(lifrp, sizeof (struct lifreq));
	lnr_addr = &lifrp->lifr_nd.lnr_addr;

	if (ndp_iface != NULL) {
		(void) strlcpy(lifr_name, ndp_iface, LIFNAMSIZ);
	} else if (sin6p->sin6_scope_id != 0) {
		int zone_id = sin6p->sin6_scope_id;
		if (if_indextoname(zone_id, lifr_name) == NULL) {
			warnx("Invalid zone identifier: %d", zone_id);
			return (-1);
		}
	} else if (IN6_IS_ADDR_LINKSCOPE(&sin6p->sin6_addr)) {
		warnx("Link-scope addresses should specify an interface with "
		    "a zone ID, or with -i.");
		return (-1);
	} else {
		if (ndp_find_interface(route, sap, lifr_name, LIFNAMSIZ) != 0)
			return (-1);
	}

	(void) memcpy(lnr_addr, sap, sizeof (struct sockaddr_storage));

	return (0);
}

/*
 * Take a host identifier, find the corresponding IPv6 addresses and then pass
 * them to the specified function, along with any desired data.
 */
static int
ndp_host_enumerate(char *host, ndp_addr_f *addr_func, void *data)
{
	struct lifreq lifr;
	struct addrinfo hints, *serverinfo, *p;
	int err, attempts = 0;
	int inet6, route;

	bzero(&hints, sizeof (struct addrinfo));
	hints.ai_family = AF_INET6;
	hints.ai_protocol = IPPROTO_IPV6;

	while (attempts < MAX_ATTEMPTS) {
		err = getaddrinfo(host, NULL, &hints, &serverinfo);

		if (err == 0) {
			break;
		} else if (err == EAI_AGAIN) {
			attempts++;
		} else {
			warnx("Unable to lookup %s: %s", host,
			    gai_strerror(err));
			return (-1);
		}
	}

	if (attempts == MAX_ATTEMPTS) {
		warnx("Failed multiple times to lookup %s", host);
		return (-1);
	}

	inet6 = socket(PF_INET6, SOCK_DGRAM, 0);
	if (inet6 < 0) {
		warnx("Failed to open IPv6 socket: %s", strerror(errno));
		err = -1;
	}

	route = socket(PF_ROUTE, SOCK_RAW, 0);
	if (route < 0) {
		warnx("Failed to open routing socket: %s", strerror(errno));
		err = -1;
	}

	if (err == 0) {
		for (p = serverinfo; p != NULL; p = p->ai_next) {
			if (ndp_initialize_lifreq(route, &lifr, p->ai_addr)
			    != 0) {
				err = -1;
				continue;
			}

			if (addr_func(inet6, &lifr, data) != 0) {
				err = -1;
				continue;
			}
		}
	}

	if (close(route) != 0) {
		warnx("Failed to close routing socket: %s", strerror(errno));
		err = -1;
	}

	if (close(inet6) != 0) {
		warnx("Failed to close IPv6 socket: %s", strerror(errno));
		err = -1;
	}

	/* Clean up linked list */
	freeaddrinfo(serverinfo);

	return (err);
}

static int
ndp_display(struct lifreq *lifrp)
{
	struct sockaddr_in6 *lnr_addr;
	char ipaddr[INET6_ADDRSTRLEN];
	char *lladdr = NULL;
	char hostname[NI_MAXHOST];
	int flags, gni_flags;

	lnr_addr = (struct sockaddr_in6 *)&lifrp->lifr_nd.lnr_addr;
	flags = lifrp->lifr_nd.lnr_flags;

	if (inet_ntop(AF_INET6, &lnr_addr->sin6_addr, ipaddr,
	    sizeof (ipaddr)) == NULL) {
		warnx("Couldn't convert IPv6 address to string: %s",
		    strerror(errno));
		return (-1);
	};

	if ((lladdr = _link_ntoa((uchar_t *)lifrp->lifr_nd.lnr_hdw_addr,
	    NULL, lifrp->lifr_nd.lnr_hdw_len, IFT_ETHER)) == NULL) {
		warnx("Couldn't convert link-layer address to string: %s",
		    strerror(errno));
		return (-1);
	}

	gni_flags = ndp_noresolve ? NI_NUMERICHOST : 0;

	if (getnameinfo((struct sockaddr *)lnr_addr, sizeof (sin6_t), hostname,
	    sizeof (hostname), NULL, 0, gni_flags) != 0) {
		warnx("Unable to lookup hostname for %s", ipaddr);
		free(lladdr);
		return (-1);
	}

	(void) printf("%s (%s) at %s", ipaddr, hostname, lladdr);

	if (flags & NDF_ISROUTER_ON) {
		(void) printf(" router");
	}

	if (flags & NDF_ANYCAST_ON) {
		(void) printf(" any");
	}

	if (!(flags & NDF_STATIC)) {
		(void) printf(" temp");
	}

	if (flags & NDF_PROXY_ON) {
		(void) printf(" proxy");
	}

	(void) printf("\n");

	free(lladdr);
	return (0);
}

static int
ndp_display_missing(struct lifreq *lifrp)
{
	struct sockaddr_in6 *lnr_addr;
	char ipaddr[INET6_ADDRSTRLEN];
	char hostname[NI_MAXHOST];
	int flags = ndp_noresolve ? NI_NUMERICHOST : 0;
	lnr_addr = (struct sockaddr_in6 *)&lifrp->lifr_nd.lnr_addr;

	if (inet_ntop(AF_INET6, &lnr_addr->sin6_addr, ipaddr,
	    sizeof (ipaddr)) == NULL) {
		warnx("Couldn't convert IPv6 address to string: %s",
		    strerror(errno));
		return (-1);
	};

	if (getnameinfo((struct sockaddr *)lnr_addr, sizeof (sin6_t), hostname,
	    sizeof (hostname), NULL, 0, flags) != 0) {
		warnx("Unable to lookup hostname for %s", ipaddr);
		return (-1);
	}

	(void) printf("%s (%s) -- no entry\n", ipaddr, hostname);
	return (0);
}

static void
ndp_lifr2ip(struct lifreq *lifrp, char *ipaddr, int buflen)
{
	sin6_t *lnr_addr = (sin6_t *)&lifrp->lifr_nd.lnr_addr;
	if (inet_ntop(AF_INET6, &lnr_addr->sin6_addr, ipaddr,
	    buflen) == NULL) {
		(void) snprintf(ipaddr, buflen, "(failed to format IP)");
	};
}

/*
 * Perform a SIOCLIFGETND and print out information about it
 */
/*ARGSUSED*/
static int
ndp_get(int fd, struct lifreq *lifrp, void *unused)
{
	char ipaddr[INET6_ADDRSTRLEN];
	if (ioctl(fd, SIOCLIFGETND, lifrp) < 0) {
		if (errno == ESRCH) {
			return (ndp_display_missing(lifrp));
		} else {
			ndp_lifr2ip(lifrp, ipaddr, sizeof (ipaddr));
			warnx("Couldn't lookup %s: %s",
			    ipaddr, strerror(errno));
			return (-1);
		}
	}

	return (ndp_display(lifrp));
}

/*
 * Print out all NDP entries
 */
static void
ndp_get_all(void)
{
	(void) execl(netstat_path, "netstat",
	    (ndp_noresolve ? "-np" : "-p"),
	    "-f", "inet6", (char *)0);
	ndp_fatal("Coudn't exec %s: %s", netstat_path, strerror(errno));
}

/*
 * Perform a SIOCLIFDELND ioctl
 */
/*ARGSUSED*/
static int
ndp_delete(int fd, struct lifreq *lifrp, void *unused)
{
	char ipaddr[INET6_ADDRSTRLEN];

	if (ioctl(fd, SIOCLIFDELND, lifrp) < 0) {
		ndp_lifr2ip(lifrp, ipaddr, sizeof (ipaddr));
		if (errno == ESRCH) {
			warnx("No entry for %s", ipaddr);
			return (-1);
		} else if (errno == EPERM) {
			warnx("Permission denied, "
			    "could not delete entry for %s", ipaddr);
			return (-1);
		} else {
			warnx("Couldn't delete mapping for %s: %s",
			    ipaddr, strerror(errno));
			return (-1);
		}
	}

	return (0);
}

/*
 * Perform a SIOCLIFSETND ioctl using properties from the example structure.
 */
static int
ndp_set(int fd, struct lifreq *lifrp, void *data)
{
	char ipaddr[INET6_ADDRSTRLEN];
	const lif_nd_req_t *nd_attrs = data;

	(void) memcpy(lifrp->lifr_nd.lnr_hdw_addr, nd_attrs->lnr_hdw_addr,
	    ND_MAX_HDW_LEN);
	lifrp->lifr_nd.lnr_hdw_len = nd_attrs->lnr_hdw_len;
	lifrp->lifr_nd.lnr_flags = nd_attrs->lnr_flags;

	lifrp->lifr_nd.lnr_state_create = nd_attrs->lnr_state_create;
	lifrp->lifr_nd.lnr_state_same_lla = nd_attrs->lnr_state_same_lla;
	lifrp->lifr_nd.lnr_state_diff_lla = nd_attrs->lnr_state_diff_lla;

	if (ioctl(fd, SIOCLIFSETND, lifrp) < 0) {
		ndp_lifr2ip(lifrp, ipaddr, sizeof (ipaddr));
		if (errno == EPERM) {
			warnx("Permission denied, "
			    "could not set entry for %s", ipaddr);
			return (-1);
		} else {
			warnx("Failed to set mapping for %s: %s",
			    ipaddr, strerror(errno));
			return (-1);
		}
	}

	return (0);
}

/*
 * Given a host identifier, a link-layer address and possible options,
 * add/update the NDP mappings.
 */
static int
ndp_set_nce(char *host, char *lladdr, char *opts[], int optlen)
{
	lif_nd_req_t nd_attrs;
	uchar_t *ea;
	char *opt;
	int i;
	boolean_t temp = B_FALSE;
	boolean_t any = B_FALSE;
	boolean_t router = B_FALSE;

	bzero(&nd_attrs, sizeof (lif_nd_req_t));

	ea = _link_aton(lladdr, &nd_attrs.lnr_hdw_len);

	if (ea == NULL) {
		warnx("Unable to parse link-layer address \"%s\"", lladdr);
		return (-1);
	}

	if (nd_attrs.lnr_hdw_len > sizeof (nd_attrs.lnr_hdw_addr)) {
		warnx("The size of the link-layer address is "
		    "too large to set\n");
		free(ea);
		return (-1);
	}

	(void) memcpy(nd_attrs.lnr_hdw_addr, ea, nd_attrs.lnr_hdw_len);

	free(ea);

	nd_attrs.lnr_state_create = ND_REACHABLE;
	nd_attrs.lnr_state_same_lla = ND_UNCHANGED;
	nd_attrs.lnr_state_diff_lla = ND_STALE;

	for (i = 0; i < optlen; i++) {
		opt = opts[i];
		if (strcmp(opt, "temp") == 0) {
			temp = B_TRUE;
		} else if (strcmp(opt, "any") == 0) {
			any = B_TRUE;
		} else if (strcmp(opt, "router") == 0) {
			router = B_TRUE;
		} else if (strcmp(opt, "proxy") == 0) {
			warnx("NDP proxying is currently not supported");
			return (-1);
		} else {
			warnx("Unrecognized option \"%s\"", opt);
			return (-1);
		}
	}

	if (!temp) {
		nd_attrs.lnr_flags |= NDF_STATIC;
	}

	if (any) {
		nd_attrs.lnr_flags |= NDF_ANYCAST_ON;
	} else {
		nd_attrs.lnr_flags |= NDF_ANYCAST_OFF;
	}

	if (router) {
		nd_attrs.lnr_flags |= NDF_ISROUTER_OFF;
	} else {
		nd_attrs.lnr_flags |= NDF_ISROUTER_OFF;
	}

	return (ndp_host_enumerate(host, ndp_set, &nd_attrs));
}

/*
 * Read in a file and set the mappings from each line.
 */
static int
ndp_set_file(char *filename)
{
	char *line = NULL, *lasts = NULL, *curr;
	char *host, *lladdr;
	char *opts[MAX_OPTS];
	int optlen = 0, lineno = 0;
	size_t cap = 0;
	boolean_t failed_line = B_FALSE;
	FILE *stream = fopen(filename, "r");

	if (stream == NULL) {
		ndp_fatal("Error while opening file %s: %s",
		    filename, strerror(errno));
	}

	errno = 0;
	while (getline(&line, &cap, stream) != -1) {
		lineno++;

		if (line[0] == '#')
			continue;

		host = strtok_r(line, WORDSEPS, &lasts);
		if (host == NULL) {
			warnx("Line %d incomplete, skipping: "
			    "missing host identifier", lineno);
			failed_line = B_TRUE;
			continue;
		}

		lladdr = strtok_r(NULL, WORDSEPS, &lasts);
		if (lladdr == NULL) {
			warnx("Line %d incomplete, skipping: "
			    "missing link-layer address", lineno);
			failed_line = B_TRUE;
			continue;
		}

		for (optlen = 0; optlen < MAX_OPTS; optlen++) {
			curr = strtok_r(NULL, WORDSEPS, &lasts);
			if (curr == NULL)
				break;
			opts[optlen] = curr;
		}

		if (ndp_set_nce(host, lladdr, opts, optlen) != 0) {
			failed_line = B_TRUE;
			continue;
		}
	}

	free(line);

	if (errno != 0 || ferror(stream)) {
		ndp_fatal("Error while reading from file %s: %s", filename,
		    strerror(errno));
	}

	if (fclose(stream) != 0) {
		ndp_fatal("Error close file %s: %s", filename, strerror(errno));
	}

	return (failed_line ? -1 : 0);
}

int
main(int argc, char *argv[])
{
	char *flagarg = NULL, *lladdr = NULL;
	char **opts;
	char *endptr;
	int c, argsleft, optlen = 0, err = 0;
	long long period;
	enum ndp_action action = NDP_A_DEFAULT;

	setprogname(basename(argv[0]));

	if (argc < 2) {
		ndp_usage("No arguments given.");
	}

	while ((c = getopt(argc, argv, ":naA:d:f:i:s:")) != -1) {
		switch (c) {
		case 'n':
			ndp_noresolve = B_TRUE;
			break;
		case 'i':
			ndp_iface = optarg;
			break;
		case 's':
			if (action != NDP_A_DEFAULT)
				ndp_badflag(action);
			action = NDP_A_SET_NCE;
			flagarg = optarg;

			if ((argc - optind) < 1) {
				ndp_usage("Missing link-layer address after "
				    "the node address, \"%s\"", flagarg);
			}
			lladdr = argv[optind++];

			/*
			 * Grab any following keywords up to the next flag
			 */
			opts = argv + optind;
			while ((argc - optind) > 0) {
				if (argv[optind][0] == '-')
					ndp_usage("Encountered \"%s\" after "
					    "flag parsing is done",
					    argv[optind]);
				optind++;
				optlen++;
			}
			break;
		case 'a':
			if (action != NDP_A_DEFAULT)
				ndp_badflag(action);
			action = NDP_A_GET_ALL;
			break;
		case 'A':
			if (action != NDP_A_DEFAULT)
				ndp_badflag(action);
			action = NDP_A_GET_FOREVER;
			flagarg = optarg;
			break;
		case 'd':
			if (action != NDP_A_DEFAULT)
				ndp_badflag(action);
			action = NDP_A_DELETE;
			flagarg = optarg;
			break;
		case 'f':
			if (action != NDP_A_DEFAULT)
				ndp_badflag(action);
			action = NDP_A_SET_FILE;
			flagarg = optarg;
			break;
		case ':':
			ndp_missingarg(optopt);
			break;
		case '?':
			ndp_usage("Unrecognized flag \"-%c\"", optopt);
		default:
			ndp_usage(NULL);
		}
	}

	argsleft = argc - optind;
	ndp_pid = getpid();

	if (action != NDP_A_DEFAULT && argsleft != 0) {
		ndp_usage("Extra arguments leftover after parsing flags");
	}

	switch (action) {
	case NDP_A_DEFAULT:
	case NDP_A_GET:
		if (argsleft != 1) {
			ndp_usage("Multiple arguments given without any flags");
		}
		err = ndp_host_enumerate(argv[optind], ndp_get, NULL);
		break;
	case NDP_A_GET_ALL:
		ndp_get_all();
		/*NOTREACHED*/
		break;
	case NDP_A_GET_FOREVER:
		errno = 0;
		period = strtoll(flagarg, &endptr, 10);
		if ((period == 0 && errno != 0) ||
		    (endptr[0] != '\0') ||
		    (period < 0)) {
			ndp_usage("Given period should be a positive integer,"
			    " not \"%s\"", flagarg);
		}
		if (period > 86400) {
			ndp_usage("Given period should be shorter than a day;"
			    " given \"%s\" seconds", flagarg);
		}
		ndp_run_periodically(period, ndp_get_all);
		/*NOTREACHED*/
		break;
	case NDP_A_DELETE:
		err = ndp_host_enumerate(flagarg, ndp_delete, NULL);
		break;
	case NDP_A_SET_NCE:
		err = ndp_set_nce(flagarg, lladdr, opts, optlen);
		break;
	case NDP_A_SET_FILE:
		err = ndp_set_file(flagarg);
		break;
	}

	return (err == 0 ? 0 : 1);
}
