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(void);
  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(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  */
 119 static void
 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
 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
 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
 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
 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
 252 ndp_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  */
 262 static void
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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 }