xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/ndp.c (revision 30699046)
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 
44 typedef	struct	sockaddr_in6	sin6_t;
45 
46 #define	BUF_SIZE 2048
47 typedef struct rtmsg_pkt {
48 	struct	rt_msghdr m_rtm;
49 	char	m_space[BUF_SIZE];
50 } rtmsg_pkt_t;
51 
52 enum 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 
62 typedef	int	(ndp_addr_f)(int, struct lifreq *, void *);
63 typedef	void	(ndp_void_f)(void);
64 
65 static	void	ndp_usage(const char *, ...);
66 static	void	ndp_fatal(const char *, ...);
67 static	void	ndp_badflag(enum ndp_action);
68 static	void	ndp_missingarg(char);
69 
70 static	void	ndp_run_in_child(ndp_void_f *);
71 static	void	ndp_do_run(int);
72 static	void	ndp_setup_handler(sigset_t *);
73 static	void	ndp_start_timer(time_t period);
74 static	void	ndp_run_periodically(time_t, ndp_void_f *);
75 
76 static	int	ndp_salen(const struct sockaddr *sa);
77 static	int	ndp_extract_sockaddrs(struct rt_msghdr *, struct sockaddr **,
78 		    struct sockaddr **, struct sockaddr **, struct sockaddr **,
79 		    struct sockaddr_dl **);
80 static	int	ndp_rtmsg_get(int, rtmsg_pkt_t *, struct sockaddr *);
81 static	int	ndp_find_interface(int, struct sockaddr *, char *, int);
82 
83 static	int	ndp_initialize_lifreq(int, struct lifreq *, struct sockaddr *);
84 static	int	ndp_host_enumerate(char *, ndp_addr_f *, void *);
85 
86 static	int	ndp_display(struct lifreq *);
87 static	int	ndp_display_missing(struct lifreq *);
88 static	void	ndp_lifr2ip(struct lifreq *, char *, int);
89 
90 static	int	ndp_get(int, struct lifreq *, void *);
91 static	void	ndp_get_all(void);
92 static	int	ndp_delete(int, struct lifreq *, void *);
93 static	int	ndp_set(int, struct lifreq *, void *);
94 static	int	ndp_set_nce(char *, char *, char *[], int);
95 static	int	ndp_set_file(char *);
96 
97 static	char		*ndp_iface = NULL;
98 static	char		*netstat_path = "/usr/bin/netstat";
99 static	pid_t		ndp_pid;
100 static	boolean_t	ndp_noresolve = B_FALSE; /* Don't lookup addresses */
101 static	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(8) 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  */
119 static void
ndp_fatal(const char * format,...)120 ndp_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  */
134 static void
ndp_usage(const char * reason,...)135 ndp_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 
160 static void
ndp_badflag(enum ndp_action action)161 ndp_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 
192 static void
ndp_missingarg(char flag)193 ndp_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  */
221 static void
ndp_run_in_child(ndp_void_f * func)222 ndp_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  */
251 static void
ndp_do_run(int signal __unused)252 ndp_do_run(int signal __unused)
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  */
262 static void
ndp_setup_handler(sigset_t * oset)263 ndp_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  */
298 static void
ndp_start_timer(time_t period)299 ndp_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  */
319 static void
ndp_run_periodically(time_t period,ndp_void_f * func)320 ndp_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  */
345 static int
ndp_salen(const struct sockaddr * sa)346 ndp_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  */
368 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)369 ndp_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  */
427 static int
ndp_rtmsg_get(int fd,rtmsg_pkt_t * msg,struct sockaddr * sin6p)428 ndp_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  */
499 static int
ndp_find_interface(int fd,struct sockaddr * sin6p,char * buf,int buflen)500 ndp_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  */
536 static int
ndp_initialize_lifreq(int route,struct lifreq * lifrp,struct sockaddr * sap)537 ndp_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  */
573 static int
ndp_host_enumerate(char * host,ndp_addr_f * addr_func,void * data)574 ndp_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 
647 static int
ndp_display(struct lifreq * lifrp)648 ndp_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 
706 static int
ndp_display_missing(struct lifreq * lifrp)707 ndp_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 
732 static void
ndp_lifr2ip(struct lifreq * lifrp,char * ipaddr,int buflen)733 ndp_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*/
746 static int
ndp_get(int fd,struct lifreq * lifrp,void * unused)747 ndp_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  */
767 static void
ndp_get_all(void)768 ndp_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*/
780 static int
ndp_delete(int fd,struct lifreq * lifrp,void * unused)781 ndp_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  */
807 static int
ndp_set(int fd,struct lifreq * lifrp,void * data)808 ndp_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  */
842 static int
ndp_set_nce(char * host,char * lladdr,char * opts[],int optlen)843 ndp_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  */
916 static int
ndp_set_file(char * filename)917 ndp_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 
982 int
main(int argc,char * argv[])983 main(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