root/usr/src/cmd/cmd-inet/usr.sbin/ndp.c
/*
 * 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);
}