1/*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source.  A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12/*
13 * Copyright 2015 Joyent, Inc.  All rights reserved.
14 */
15
16/*
17 * ndp - display and manipulate Neighbor Cache Entries from NDP
18 */
19
20#include <stdio.h>
21#include <stdarg.h>
22#include <signal.h>
23#include <time.h>
24#include <err.h>
25#include <errno.h>
26#include <stdlib.h>
27#include <strings.h>
28#include <unistd.h>
29#include <libgen.h>
30#include <sys/ioctl.h>
31#include <sys/types.h>
32#include <wait.h>
33#include <sys/mac.h>
34#include <sys/socket.h>
35#include <sys/sockio.h>
36#include <netdb.h>
37#include <net/if_types.h>
38#include <netinet/in.h>
39#include <arpa/inet.h>
40#include <inet/ip.h>
41#include <net/if_dl.h>
42#include <net/route.h>
43
44typedef	struct	sockaddr_in6	sin6_t;
45
46#define	BUF_SIZE 2048
47typedef struct rtmsg_pkt {
48	struct	rt_msghdr m_rtm;
49	char	m_space[BUF_SIZE];
50} rtmsg_pkt_t;
51
52enum ndp_action {
53	NDP_A_DEFAULT,
54	NDP_A_GET,		/* Show a single NDP entry */
55	NDP_A_GET_ALL,		/* Show NDP entries */
56	NDP_A_GET_FOREVER,	/* Repeatedly show entries */
57	NDP_A_DELETE,		/* Delete an NDP entry */
58	NDP_A_SET_NCE,		/* Set NDP entry */
59	NDP_A_SET_FILE		/* Read in & set NDP entries */
60};
61
62typedef	int	(ndp_addr_f)(int, struct lifreq *, void *);
63typedef	void	(ndp_void_f)(void);
64
65static	void	ndp_usage(const char *, ...);
66static	void	ndp_fatal(const char *, ...);
67static	void	ndp_badflag(enum ndp_action);
68static	void	ndp_missingarg(char);
69
70static	void	ndp_run_in_child(ndp_void_f *);
71static	void	ndp_do_run(void);
72static	void	ndp_setup_handler(sigset_t *);
73static	void	ndp_start_timer(time_t period);
74static	void	ndp_run_periodically(time_t, ndp_void_f *);
75
76static	int	ndp_salen(const struct sockaddr *sa);
77static	int	ndp_extract_sockaddrs(struct rt_msghdr *, struct sockaddr **,
78		    struct sockaddr **, struct sockaddr **, struct sockaddr **,
79		    struct sockaddr_dl **);
80static	int	ndp_rtmsg_get(int, rtmsg_pkt_t *, struct sockaddr *);
81static	int	ndp_find_interface(int, struct sockaddr *, char *, int);
82
83static	int	ndp_initialize_lifreq(int, struct lifreq *, struct sockaddr *);
84static	int	ndp_host_enumerate(char *, ndp_addr_f *, void *);
85
86static	int	ndp_display(struct lifreq *);
87static	int	ndp_display_missing(struct lifreq *);
88static	void	ndp_lifr2ip(struct lifreq *, char *, int);
89
90static	int	ndp_get(int, struct lifreq *, void *);
91static	void	ndp_get_all(void);
92static	int	ndp_delete(int, struct lifreq *, void *);
93static	int	ndp_set(int, struct lifreq *, void *);
94static	int	ndp_set_nce(char *, char *, char *[], int);
95static	int	ndp_set_file(char *);
96
97static	char		*ndp_iface = NULL;
98static	char		*netstat_path = "/usr/bin/netstat";
99static	pid_t		ndp_pid;
100static	boolean_t	ndp_noresolve = B_FALSE; /* Don't lookup addresses */
101static	boolean_t	ndp_run = B_TRUE;
102
103#define	MAX_ATTEMPTS 5
104#define	MAX_OPTS 5
105#define	WORDSEPS " \t\r\n"
106
107/*
108 * Macros borrowed from route(1M) for working with PF_ROUTE messages
109 */
110#define	RT_ADVANCE(x, n) ((x) += ndp_salen(n))
111#define	RT_NEXTADDR(cp, w, u) \
112	l = ndp_salen(u); \
113	(void) memmove(cp, u, l); \
114	cp += l;
115
116/*
117 * Print an error to stderr and then exit non-zero.
118 */
119static void
120ndp_fatal(const char *format, ...)
121{
122	va_list ap;
123
124	va_start(ap, format);
125	vwarnx(format, ap);
126	va_end(ap);
127	exit(EXIT_FAILURE);
128}
129
130/*
131 * Print out the command usage to stderr, along with any reason why it's being
132 * printed, and then exit non-zero.
133 */
134static void
135ndp_usage(const char *reason, ...)
136{
137	va_list ap;
138	const char *ndp_progname = getprogname();
139
140	if (reason != NULL) {
141		va_start(ap, reason);
142		(void) fprintf(stderr, "%s: ", ndp_progname);
143		(void) vfprintf(stderr, reason, ap);
144		(void) fprintf(stderr, "\n");
145		va_end(ap);
146	}
147
148	(void) fprintf(stderr,
149	    "Usage: %s [-n] [-i iface] hostname\n"
150	    "       %s [-n] [-i iface] -s nodeaddr etheraddr [temp] [proxy]\n"
151	    "       %s [-n] [-i iface] -d nodeaddr\n"
152	    "       %s [-n] [-i iface] -f filename\n"
153	    "       %s [-n] -a\n"
154	    "       %s [-n] -A period\n",
155	    ndp_progname, ndp_progname, ndp_progname,
156	    ndp_progname, ndp_progname, ndp_progname);
157	exit(EXIT_FAILURE);
158}
159
160static void
161ndp_badflag(enum ndp_action action)
162{
163	switch (action) {
164	case NDP_A_DEFAULT:
165	case NDP_A_GET:
166		ndp_usage("Already going to print an entry, "
167		    "but extra -%c given", optopt);
168		break;
169	case NDP_A_GET_ALL:
170		ndp_usage("Already going to print all entries (-a), "
171		    "but extra -%c given", optopt);
172		break;
173	case NDP_A_GET_FOREVER:
174		ndp_usage("Already going to repeatedly print all entries (-A), "
175		    "but extra -%c given", optopt);
176		break;
177	case NDP_A_DELETE:
178		ndp_usage("Already going to delete an entry (-d), "
179		    "but extra -%c given", optopt);
180		break;
181	case NDP_A_SET_NCE:
182		ndp_usage("Already going to set an entry (-s), "
183		    "but extra -%c given", optopt);
184		break;
185	case NDP_A_SET_FILE:
186		ndp_usage("Already going to set from file (-f), "
187		    "but extra -%c given", optopt);
188		break;
189	}
190}
191
192static void
193ndp_missingarg(char flag)
194{
195	switch (flag) {
196	case 'A':
197		ndp_usage("Missing time period after -%c", flag);
198		break;
199	case 'd':
200		ndp_usage("Missing node name after -%c", flag);
201		break;
202	case 'f':
203		ndp_usage("Missing filename after -%c", flag);
204		break;
205	case 's':
206		ndp_usage("Missing node name after -%c", flag);
207		break;
208	case 'i':
209		ndp_usage("Missing interface name after -%c", flag);
210		break;
211	default:
212		ndp_usage("Missing option argument after -%c", flag);
213		break;
214	}
215}
216
217/*
218 * Run a function that's going to exec in a child process, and don't return
219 * until it exits.
220 */
221static void
222ndp_run_in_child(ndp_void_f *func)
223{
224	pid_t child_pid;
225	int childstat = 0, status = 0;
226
227	child_pid = fork();
228	if (child_pid == (pid_t)-1) {
229		ndp_fatal("Unable to fork: %s", strerror(errno));
230	} else if (child_pid == (pid_t)0) {
231		func();
232		exit(EXIT_FAILURE);
233	}
234
235	while (waitpid(child_pid, &childstat, 0) == -1) {
236		if (errno == EINTR)
237			continue;
238
239		ndp_fatal("Failed to wait on child: %s", strerror(errno));
240	}
241
242	status = WEXITSTATUS(childstat);
243	if (status != 0) {
244		ndp_fatal("Child process exited with %d", status);
245	}
246}
247
248/*
249 * SIGALRM handler to schedule a run.
250 */
251static void
252ndp_do_run(void)
253{
254	ndp_run = B_TRUE;
255}
256
257
258/*
259 * Prepare signal masks, and install the SIGALRM handler. Return old signal
260 * masks through the first argument.
261 */
262static void
263ndp_setup_handler(sigset_t *oset)
264{
265	struct sigaction sa;
266
267	/*
268	 * Mask off SIGALRM so we only trigger the handler when we're ready
269	 * using sigsuspend(3C), in case the child process takes longer to
270	 * run than the alarm interval.
271	 */
272	if (sigprocmask(0, NULL, oset) != 0) {
273		ndp_fatal("Unable to set signal mask: %s", strerror(errno));
274	}
275
276	if (sighold(SIGALRM) != 0) {
277		ndp_fatal("Unable to add SIGALRM to signal mask: %s",
278		    strerror(errno));
279	}
280
281	sa.sa_flags = 0;
282	sa.sa_handler = ndp_do_run;
283
284	if (sigemptyset(&sa.sa_mask) != 0) {
285		ndp_fatal("Unable to prepare empty signal set: %s",
286		    strerror(errno));
287	}
288
289	if (sigaction(SIGALRM, &sa, NULL) != 0) {
290		ndp_fatal("Unable to install timer handler: %s",
291		    strerror(errno));
292	}
293}
294
295/*
296 * Start the printing timer.
297 */
298static void
299ndp_start_timer(time_t period)
300{
301	timer_t timer;
302	struct itimerspec interval;
303	interval.it_value.tv_sec  = interval.it_interval.tv_sec  = period;
304	interval.it_value.tv_nsec = interval.it_interval.tv_nsec = 0;
305
306	if (timer_create(CLOCK_REALTIME, NULL, &timer) != 0) {
307		ndp_fatal("Unable to create timer: %s", strerror(errno));
308	}
309
310	if (timer_settime(timer, 0, &interval, NULL) != 0) {
311		ndp_fatal("Unable to set time on timer: %s", strerror(errno));
312	}
313}
314
315
316/*
317 * Run a given function forever periodically in a child process.
318 */
319static void
320ndp_run_periodically(time_t period, ndp_void_f *func)
321{
322	sigset_t oset;
323
324	ndp_setup_handler(&oset);
325	ndp_start_timer(period);
326
327	do {
328		if (ndp_run) {
329			ndp_run = B_FALSE;
330			ndp_run_in_child(func);
331		}
332		(void) sigsuspend(&oset);
333	} while (errno == EINTR);
334
335	/*
336	 * Only an EFAULT should get us here. Abort so we get a core dump.
337	 */
338	warnx("Failure while waiting on timer: %s", strerror(errno));
339	abort();
340}
341
342/*
343 * Given an address, return its size.
344 */
345static int
346ndp_salen(const struct sockaddr *sa)
347{
348	switch (sa->sa_family) {
349	case AF_INET:
350		return (sizeof (struct sockaddr_in));
351	case AF_LINK:
352		return (sizeof (struct sockaddr_dl));
353	case AF_INET6:
354		return (sizeof (struct sockaddr_in6));
355	default:
356		warnx("Unrecognized sockaddr with address family %d!",
357		    sa->sa_family);
358		abort();
359	}
360	/*NOTREACHED*/
361}
362
363/*
364 * Extract all socket addresses from a routing message, and return them
365 * through the pointers given as arguments to ndp_extract_sockaddrs. None
366 * of the pointers should be null.
367 */
368static int
369ndp_extract_sockaddrs(struct rt_msghdr *rtm, struct sockaddr **dst,
370    struct sockaddr **gate, struct sockaddr **mask, struct sockaddr **src,
371    struct sockaddr_dl **ifp)
372{
373	struct sockaddr *sa;
374	char *cp;
375	int i;
376
377	if (rtm->rtm_version != RTM_VERSION) {
378		warnx("Routing message version %d not understood",
379		    rtm->rtm_version);
380		return (-1);
381	}
382
383	if (rtm->rtm_errno != 0)  {
384		warnx("Routing message couldn't be processed: %s",
385		    strerror(rtm->rtm_errno));
386		return (-1);
387	}
388
389	cp = ((char *)(rtm + 1));
390	if (rtm->rtm_addrs != 0) {
391		for (i = 1; i != 0; i <<= 1) {
392			if ((i & rtm->rtm_addrs) == 0)
393				continue;
394
395			/*LINTED*/
396			sa = (struct sockaddr *)cp;
397			switch (i) {
398			case RTA_DST:
399				*dst = sa;
400				break;
401			case RTA_GATEWAY:
402				*gate = sa;
403				break;
404			case RTA_NETMASK:
405				*mask = sa;
406				break;
407			case RTA_IFP:
408				if (sa->sa_family == AF_LINK &&
409				    ((struct sockaddr_dl *)sa)->sdl_nlen != 0)
410					*ifp = (struct sockaddr_dl *)sa;
411				break;
412			case RTA_SRC:
413				*src = sa;
414				break;
415			}
416			RT_ADVANCE(cp, sa);
417		}
418	}
419
420	return (0);
421}
422
423/*
424 * Given an IPv6 address, use routing information to look up
425 * the destination and interface it would pass through.
426 */
427static int
428ndp_rtmsg_get(int fd, rtmsg_pkt_t *msg, struct sockaddr *sin6p)
429{
430	static int seq = 0;
431	struct sockaddr_dl sdl;
432	int mlen, l;
433	char ipaddr[INET6_ADDRSTRLEN];
434	char *cp = msg->m_space;
435	struct	rt_msghdr *m_rtm = &msg->m_rtm;
436
437	bzero(msg, sizeof (rtmsg_pkt_t));
438	bzero(&sdl, sizeof (struct sockaddr_dl));
439
440	m_rtm->rtm_type = RTM_GET;
441	m_rtm->rtm_version = RTM_VERSION;
442	m_rtm->rtm_seq = ++seq;
443	m_rtm->rtm_addrs = RTA_DST | RTA_IFP;
444	m_rtm->rtm_msglen = sizeof (rtmsg_pkt_t);
445
446	/* Place the address we're looking up after the header */
447	RT_NEXTADDR(cp, RTA_DST, sin6p);
448
449	/* Load an empty link-level address, so we get an interface back */
450	sdl.sdl_family = AF_LINK;
451	RT_NEXTADDR(cp, RTA_IFP, (struct sockaddr *)&sdl);
452
453	m_rtm->rtm_msglen = cp - (char *)msg;
454
455	if ((mlen = write(fd, (char *)msg, m_rtm->rtm_msglen)) < 0) {
456		if (errno == ESRCH) {
457			/*LINTED*/
458			if (inet_ntop(AF_INET6, &((sin6_t *)sin6p)->sin6_addr,
459			    ipaddr, sizeof (ipaddr)) == NULL) {
460				(void) snprintf(ipaddr, sizeof (ipaddr),
461				    "(failed to format IP)");
462			};
463			warnx("An appropriate interface for the address %s "
464			    "is not in the routing table; use -i to force an "
465			    "interface", ipaddr);
466			return (-1);
467		} else {
468			warnx("Failed to send routing message: %s",
469			    strerror(errno));
470			return (-1);
471		}
472	} else if (mlen < (int)m_rtm->rtm_msglen) {
473		warnx("Failed to write all bytes to routing socket");
474		return (-1);
475	}
476
477	/*
478	 * Keep reading routing messages until we find the response to the one
479	 * we just sent. Note that we depend on the sequence number being unique
480	 * to the running program.
481	 */
482	do {
483		mlen = read(fd, (char *)msg, sizeof (rtmsg_pkt_t));
484	} while (mlen > 0 &&
485	    (m_rtm->rtm_seq != seq || m_rtm->rtm_pid != ndp_pid));
486	if (mlen < 0) {
487		warnx("Failed to read from routing socket: %s",
488		    strerror(errno));
489		return (-1);
490	}
491
492	return (0);
493}
494
495/*
496 * Find the interface that the IPv6 address would be routed through, and store
497 * the name of the interface in the buffer passed in.
498 */
499static int
500ndp_find_interface(int fd, struct sockaddr *sin6p, char *buf, int buflen)
501{
502	struct sockaddr *dst = NULL, *gate = NULL, *mask = NULL, *src = NULL;
503	struct sockaddr_dl *ifp = NULL;
504	rtmsg_pkt_t msg;
505
506	if (ndp_rtmsg_get(fd, &msg, sin6p) != 0) {
507		return (-1);
508	}
509
510	if (ndp_extract_sockaddrs(&msg.m_rtm, &dst, &gate,
511	    &mask, &src, &ifp) != 0) {
512		return (-1);
513	}
514
515	if (ifp == NULL) {
516		warnx("Unable to find appropriate interface for address");
517		return (-1);
518	} else {
519		if (ifp->sdl_nlen >= buflen) {
520			warnx("The interface name \"%.*s\" is too big for the "
521			    "available buffer", ifp->sdl_nlen, ifp->sdl_data);
522			return (-1);
523		} else {
524			(void) snprintf(buf, buflen, "%.*s", ifp->sdl_nlen,
525			    ifp->sdl_data);
526		}
527	}
528
529	return (0);
530}
531
532/*
533 * Zero out a lifreq struct for a SIOCLIF*ND ioctl, set the address, and fetch
534 * the appropriate interface using the given routing socket.
535 */
536static int
537ndp_initialize_lifreq(int route, struct lifreq *lifrp, struct sockaddr *sap)
538{
539	struct sockaddr_storage *lnr_addr;
540	/* LINTED E_BAD_PTR_CAST_ALIGN */
541	struct sockaddr_in6 *sin6p = (sin6_t *)sap;
542	char *lifr_name = lifrp->lifr_name;
543
544	bzero(lifrp, sizeof (struct lifreq));
545	lnr_addr = &lifrp->lifr_nd.lnr_addr;
546
547	if (ndp_iface != NULL) {
548		(void) strlcpy(lifr_name, ndp_iface, LIFNAMSIZ);
549	} else if (sin6p->sin6_scope_id != 0) {
550		int zone_id = sin6p->sin6_scope_id;
551		if (if_indextoname(zone_id, lifr_name) == NULL) {
552			warnx("Invalid zone identifier: %d", zone_id);
553			return (-1);
554		}
555	} else if (IN6_IS_ADDR_LINKSCOPE(&sin6p->sin6_addr)) {
556		warnx("Link-scope addresses should specify an interface with "
557		    "a zone ID, or with -i.");
558		return (-1);
559	} else {
560		if (ndp_find_interface(route, sap, lifr_name, LIFNAMSIZ) != 0)
561			return (-1);
562	}
563
564	(void) memcpy(lnr_addr, sap, sizeof (struct sockaddr_storage));
565
566	return (0);
567}
568
569/*
570 * Take a host identifier, find the corresponding IPv6 addresses and then pass
571 * them to the specified function, along with any desired data.
572 */
573static int
574ndp_host_enumerate(char *host, ndp_addr_f *addr_func, void *data)
575{
576	struct lifreq lifr;
577	struct addrinfo hints, *serverinfo, *p;
578	int err, attempts = 0;
579	int inet6, route;
580
581	bzero(&hints, sizeof (struct addrinfo));
582	hints.ai_family = AF_INET6;
583	hints.ai_protocol = IPPROTO_IPV6;
584
585	while (attempts < MAX_ATTEMPTS) {
586		err = getaddrinfo(host, NULL, &hints, &serverinfo);
587
588		if (err == 0) {
589			break;
590		} else if (err == EAI_AGAIN) {
591			attempts++;
592		} else {
593			warnx("Unable to lookup %s: %s", host,
594			    gai_strerror(err));
595			return (-1);
596		}
597	}
598
599	if (attempts == MAX_ATTEMPTS) {
600		warnx("Failed multiple times to lookup %s", host);
601		return (-1);
602	}
603
604	inet6 = socket(PF_INET6, SOCK_DGRAM, 0);
605	if (inet6 < 0) {
606		warnx("Failed to open IPv6 socket: %s", strerror(errno));
607		err = -1;
608	}
609
610	route = socket(PF_ROUTE, SOCK_RAW, 0);
611	if (route < 0) {
612		warnx("Failed to open routing socket: %s", strerror(errno));
613		err = -1;
614	}
615
616	if (err == 0) {
617		for (p = serverinfo; p != NULL; p = p->ai_next) {
618			if (ndp_initialize_lifreq(route, &lifr, p->ai_addr)
619			    != 0) {
620				err = -1;
621				continue;
622			}
623
624			if (addr_func(inet6, &lifr, data) != 0) {
625				err = -1;
626				continue;
627			}
628		}
629	}
630
631	if (close(route) != 0) {
632		warnx("Failed to close routing socket: %s", strerror(errno));
633		err = -1;
634	}
635
636	if (close(inet6) != 0) {
637		warnx("Failed to close IPv6 socket: %s", strerror(errno));
638		err = -1;
639	}
640
641	/* Clean up linked list */
642	freeaddrinfo(serverinfo);
643
644	return (err);
645}
646
647static int
648ndp_display(struct lifreq *lifrp)
649{
650	struct sockaddr_in6 *lnr_addr;
651	char ipaddr[INET6_ADDRSTRLEN];
652	char *lladdr = NULL;
653	char hostname[NI_MAXHOST];
654	int flags, gni_flags;
655
656	lnr_addr = (struct sockaddr_in6 *)&lifrp->lifr_nd.lnr_addr;
657	flags = lifrp->lifr_nd.lnr_flags;
658
659	if (inet_ntop(AF_INET6, &lnr_addr->sin6_addr, ipaddr,
660	    sizeof (ipaddr)) == NULL) {
661		warnx("Couldn't convert IPv6 address to string: %s",
662		    strerror(errno));
663		return (-1);
664	};
665
666	if ((lladdr = _link_ntoa((uchar_t *)lifrp->lifr_nd.lnr_hdw_addr,
667	    NULL, lifrp->lifr_nd.lnr_hdw_len, IFT_ETHER)) == NULL) {
668		warnx("Couldn't convert link-layer address to string: %s",
669		    strerror(errno));
670		return (-1);
671	}
672
673	gni_flags = ndp_noresolve ? NI_NUMERICHOST : 0;
674
675	if (getnameinfo((struct sockaddr *)lnr_addr, sizeof (sin6_t), hostname,
676	    sizeof (hostname), NULL, 0, gni_flags) != 0) {
677		warnx("Unable to lookup hostname for %s", ipaddr);
678		free(lladdr);
679		return (-1);
680	}
681
682	(void) printf("%s (%s) at %s", ipaddr, hostname, lladdr);
683
684	if (flags & NDF_ISROUTER_ON) {
685		(void) printf(" router");
686	}
687
688	if (flags & NDF_ANYCAST_ON) {
689		(void) printf(" any");
690	}
691
692	if (!(flags & NDF_STATIC)) {
693		(void) printf(" temp");
694	}
695
696	if (flags & NDF_PROXY_ON) {
697		(void) printf(" proxy");
698	}
699
700	(void) printf("\n");
701
702	free(lladdr);
703	return (0);
704}
705
706static int
707ndp_display_missing(struct lifreq *lifrp)
708{
709	struct sockaddr_in6 *lnr_addr;
710	char ipaddr[INET6_ADDRSTRLEN];
711	char hostname[NI_MAXHOST];
712	int flags = ndp_noresolve ? NI_NUMERICHOST : 0;
713	lnr_addr = (struct sockaddr_in6 *)&lifrp->lifr_nd.lnr_addr;
714
715	if (inet_ntop(AF_INET6, &lnr_addr->sin6_addr, ipaddr,
716	    sizeof (ipaddr)) == NULL) {
717		warnx("Couldn't convert IPv6 address to string: %s",
718		    strerror(errno));
719		return (-1);
720	};
721
722	if (getnameinfo((struct sockaddr *)lnr_addr, sizeof (sin6_t), hostname,
723	    sizeof (hostname), NULL, 0, flags) != 0) {
724		warnx("Unable to lookup hostname for %s", ipaddr);
725		return (-1);
726	}
727
728	(void) printf("%s (%s) -- no entry\n", ipaddr, hostname);
729	return (0);
730}
731
732static void
733ndp_lifr2ip(struct lifreq *lifrp, char *ipaddr, int buflen)
734{
735	sin6_t *lnr_addr = (sin6_t *)&lifrp->lifr_nd.lnr_addr;
736	if (inet_ntop(AF_INET6, &lnr_addr->sin6_addr, ipaddr,
737	    buflen) == NULL) {
738		(void) snprintf(ipaddr, buflen, "(failed to format IP)");
739	};
740}
741
742/*
743 * Perform a SIOCLIFGETND and print out information about it
744 */
745/*ARGSUSED*/
746static int
747ndp_get(int fd, struct lifreq *lifrp, void *unused)
748{
749	char ipaddr[INET6_ADDRSTRLEN];
750	if (ioctl(fd, SIOCLIFGETND, lifrp) < 0) {
751		if (errno == ESRCH) {
752			return (ndp_display_missing(lifrp));
753		} else {
754			ndp_lifr2ip(lifrp, ipaddr, sizeof (ipaddr));
755			warnx("Couldn't lookup %s: %s",
756			    ipaddr, strerror(errno));
757			return (-1);
758		}
759	}
760
761	return (ndp_display(lifrp));
762}
763
764/*
765 * Print out all NDP entries
766 */
767static void
768ndp_get_all(void)
769{
770	(void) execl(netstat_path, "netstat",
771	    (ndp_noresolve ? "-np" : "-p"),
772	    "-f", "inet6", (char *)0);
773	ndp_fatal("Coudn't exec %s: %s", netstat_path, strerror(errno));
774}
775
776/*
777 * Perform a SIOCLIFDELND ioctl
778 */
779/*ARGSUSED*/
780static int
781ndp_delete(int fd, struct lifreq *lifrp, void *unused)
782{
783	char ipaddr[INET6_ADDRSTRLEN];
784
785	if (ioctl(fd, SIOCLIFDELND, lifrp) < 0) {
786		ndp_lifr2ip(lifrp, ipaddr, sizeof (ipaddr));
787		if (errno == ESRCH) {
788			warnx("No entry for %s", ipaddr);
789			return (-1);
790		} else if (errno == EPERM) {
791			warnx("Permission denied, "
792			    "could not delete entry for %s", ipaddr);
793			return (-1);
794		} else {
795			warnx("Couldn't delete mapping for %s: %s",
796			    ipaddr, strerror(errno));
797			return (-1);
798		}
799	}
800
801	return (0);
802}
803
804/*
805 * Perform a SIOCLIFSETND ioctl using properties from the example structure.
806 */
807static int
808ndp_set(int fd, struct lifreq *lifrp, void *data)
809{
810	char ipaddr[INET6_ADDRSTRLEN];
811	const lif_nd_req_t *nd_attrs = data;
812
813	(void) memcpy(lifrp->lifr_nd.lnr_hdw_addr, nd_attrs->lnr_hdw_addr,
814	    ND_MAX_HDW_LEN);
815	lifrp->lifr_nd.lnr_hdw_len = nd_attrs->lnr_hdw_len;
816	lifrp->lifr_nd.lnr_flags = nd_attrs->lnr_flags;
817
818	lifrp->lifr_nd.lnr_state_create = nd_attrs->lnr_state_create;
819	lifrp->lifr_nd.lnr_state_same_lla = nd_attrs->lnr_state_same_lla;
820	lifrp->lifr_nd.lnr_state_diff_lla = nd_attrs->lnr_state_diff_lla;
821
822	if (ioctl(fd, SIOCLIFSETND, lifrp) < 0) {
823		ndp_lifr2ip(lifrp, ipaddr, sizeof (ipaddr));
824		if (errno == EPERM) {
825			warnx("Permission denied, "
826			    "could not set entry for %s", ipaddr);
827			return (-1);
828		} else {
829			warnx("Failed to set mapping for %s: %s",
830			    ipaddr, strerror(errno));
831			return (-1);
832		}
833	}
834
835	return (0);
836}
837
838/*
839 * Given a host identifier, a link-layer address and possible options,
840 * add/update the NDP mappings.
841 */
842static int
843ndp_set_nce(char *host, char *lladdr, char *opts[], int optlen)
844{
845	lif_nd_req_t nd_attrs;
846	uchar_t *ea;
847	char *opt;
848	int i;
849	boolean_t temp = B_FALSE;
850	boolean_t any = B_FALSE;
851	boolean_t router = B_FALSE;
852
853	bzero(&nd_attrs, sizeof (lif_nd_req_t));
854
855	ea = _link_aton(lladdr, &nd_attrs.lnr_hdw_len);
856
857	if (ea == NULL) {
858		warnx("Unable to parse link-layer address \"%s\"", lladdr);
859		return (-1);
860	}
861
862	if (nd_attrs.lnr_hdw_len > sizeof (nd_attrs.lnr_hdw_addr)) {
863		warnx("The size of the link-layer address is "
864		    "too large to set\n");
865		free(ea);
866		return (-1);
867	}
868
869	(void) memcpy(nd_attrs.lnr_hdw_addr, ea, nd_attrs.lnr_hdw_len);
870
871	free(ea);
872
873	nd_attrs.lnr_state_create = ND_REACHABLE;
874	nd_attrs.lnr_state_same_lla = ND_UNCHANGED;
875	nd_attrs.lnr_state_diff_lla = ND_STALE;
876
877	for (i = 0; i < optlen; i++) {
878		opt = opts[i];
879		if (strcmp(opt, "temp") == 0) {
880			temp = B_TRUE;
881		} else if (strcmp(opt, "any") == 0) {
882			any = B_TRUE;
883		} else if (strcmp(opt, "router") == 0) {
884			router = B_TRUE;
885		} else if (strcmp(opt, "proxy") == 0) {
886			warnx("NDP proxying is currently not supported");
887			return (-1);
888		} else {
889			warnx("Unrecognized option \"%s\"", opt);
890			return (-1);
891		}
892	}
893
894	if (!temp) {
895		nd_attrs.lnr_flags |= NDF_STATIC;
896	}
897
898	if (any) {
899		nd_attrs.lnr_flags |= NDF_ANYCAST_ON;
900	} else {
901		nd_attrs.lnr_flags |= NDF_ANYCAST_OFF;
902	}
903
904	if (router) {
905		nd_attrs.lnr_flags |= NDF_ISROUTER_OFF;
906	} else {
907		nd_attrs.lnr_flags |= NDF_ISROUTER_OFF;
908	}
909
910	return (ndp_host_enumerate(host, ndp_set, &nd_attrs));
911}
912
913/*
914 * Read in a file and set the mappings from each line.
915 */
916static int
917ndp_set_file(char *filename)
918{
919	char *line = NULL, *lasts = NULL, *curr;
920	char *host, *lladdr;
921	char *opts[MAX_OPTS];
922	int optlen = 0, lineno = 0;
923	size_t cap = 0;
924	boolean_t failed_line = B_FALSE;
925	FILE *stream = fopen(filename, "r");
926
927	if (stream == NULL) {
928		ndp_fatal("Error while opening file %s: %s",
929		    filename, strerror(errno));
930	}
931
932	errno = 0;
933	while (getline(&line, &cap, stream) != -1) {
934		lineno++;
935
936		if (line[0] == '#')
937			continue;
938
939		host = strtok_r(line, WORDSEPS, &lasts);
940		if (host == NULL) {
941			warnx("Line %d incomplete, skipping: "
942			    "missing host identifier", lineno);
943			failed_line = B_TRUE;
944			continue;
945		}
946
947		lladdr = strtok_r(NULL, WORDSEPS, &lasts);
948		if (lladdr == NULL) {
949			warnx("Line %d incomplete, skipping: "
950			    "missing link-layer address", lineno);
951			failed_line = B_TRUE;
952			continue;
953		}
954
955		for (optlen = 0; optlen < MAX_OPTS; optlen++) {
956			curr = strtok_r(NULL, WORDSEPS, &lasts);
957			if (curr == NULL)
958				break;
959			opts[optlen] = curr;
960		}
961
962		if (ndp_set_nce(host, lladdr, opts, optlen) != 0) {
963			failed_line = B_TRUE;
964			continue;
965		}
966	}
967
968	free(line);
969
970	if (errno != 0 || ferror(stream)) {
971		ndp_fatal("Error while reading from file %s: %s", filename,
972		    strerror(errno));
973	}
974
975	if (fclose(stream) != 0) {
976		ndp_fatal("Error close file %s: %s", filename, strerror(errno));
977	}
978
979	return (failed_line ? -1 : 0);
980}
981
982int
983main(int argc, char *argv[])
984{
985	char *flagarg = NULL, *lladdr = NULL;
986	char **opts;
987	char *endptr;
988	int c, argsleft, optlen = 0, err = 0;
989	long long period;
990	enum ndp_action action = NDP_A_DEFAULT;
991
992	setprogname(basename(argv[0]));
993
994	if (argc < 2) {
995		ndp_usage("No arguments given.");
996	}
997
998	while ((c = getopt(argc, argv, ":naA:d:f:i:s:")) != -1) {
999		switch (c) {
1000		case 'n':
1001			ndp_noresolve = B_TRUE;
1002			break;
1003		case 'i':
1004			ndp_iface = optarg;
1005			break;
1006		case 's':
1007			if (action != NDP_A_DEFAULT)
1008				ndp_badflag(action);
1009			action = NDP_A_SET_NCE;
1010			flagarg = optarg;
1011
1012			if ((argc - optind) < 1) {
1013				ndp_usage("Missing link-layer address after "
1014				    "the node address, \"%s\"", flagarg);
1015			}
1016			lladdr = argv[optind++];
1017
1018			/*
1019			 * Grab any following keywords up to the next flag
1020			 */
1021			opts = argv + optind;
1022			while ((argc - optind) > 0) {
1023				if (argv[optind][0] == '-')
1024					ndp_usage("Encountered \"%s\" after "
1025					    "flag parsing is done",
1026					    argv[optind]);
1027				optind++;
1028				optlen++;
1029			}
1030			break;
1031		case 'a':
1032			if (action != NDP_A_DEFAULT)
1033				ndp_badflag(action);
1034			action = NDP_A_GET_ALL;
1035			break;
1036		case 'A':
1037			if (action != NDP_A_DEFAULT)
1038				ndp_badflag(action);
1039			action = NDP_A_GET_FOREVER;
1040			flagarg = optarg;
1041			break;
1042		case 'd':
1043			if (action != NDP_A_DEFAULT)
1044				ndp_badflag(action);
1045			action = NDP_A_DELETE;
1046			flagarg = optarg;
1047			break;
1048		case 'f':
1049			if (action != NDP_A_DEFAULT)
1050				ndp_badflag(action);
1051			action = NDP_A_SET_FILE;
1052			flagarg = optarg;
1053			break;
1054		case ':':
1055			ndp_missingarg(optopt);
1056			break;
1057		case '?':
1058			ndp_usage("Unrecognized flag \"-%c\"", optopt);
1059		default:
1060			ndp_usage(NULL);
1061		}
1062	}
1063
1064	argsleft = argc - optind;
1065	ndp_pid = getpid();
1066
1067	if (action != NDP_A_DEFAULT && argsleft != 0) {
1068		ndp_usage("Extra arguments leftover after parsing flags");
1069	}
1070
1071	switch (action) {
1072	case NDP_A_DEFAULT:
1073	case NDP_A_GET:
1074		if (argsleft != 1) {
1075			ndp_usage("Multiple arguments given without any flags");
1076		}
1077		err = ndp_host_enumerate(argv[optind], ndp_get, NULL);
1078		break;
1079	case NDP_A_GET_ALL:
1080		ndp_get_all();
1081		/*NOTREACHED*/
1082		break;
1083	case NDP_A_GET_FOREVER:
1084		errno = 0;
1085		period = strtoll(flagarg, &endptr, 10);
1086		if ((period == 0 && errno != 0) ||
1087		    (endptr[0] != '\0') ||
1088		    (period < 0)) {
1089			ndp_usage("Given period should be a positive integer,"
1090			    " not \"%s\"", flagarg);
1091		}
1092		if (period > 86400) {
1093			ndp_usage("Given period should be shorter than a day;"
1094			    " given \"%s\" seconds", flagarg);
1095		}
1096		ndp_run_periodically(period, ndp_get_all);
1097		/*NOTREACHED*/
1098		break;
1099	case NDP_A_DELETE:
1100		err = ndp_host_enumerate(flagarg, ndp_delete, NULL);
1101		break;
1102	case NDP_A_SET_NCE:
1103		err = ndp_set_nce(flagarg, lladdr, opts, optlen);
1104		break;
1105	case NDP_A_SET_FILE:
1106		err = ndp_set_file(flagarg);
1107		break;
1108	}
1109
1110	return (err == 0 ? 0 : 1);
1111}
1112