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