/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2015 Joyent, Inc. All rights reserved. */ /* * ndp - display and manipulate Neighbor Cache Entries from NDP */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct sockaddr_in6 sin6_t; #define BUF_SIZE 2048 typedef struct rtmsg_pkt { struct rt_msghdr m_rtm; char m_space[BUF_SIZE]; } rtmsg_pkt_t; enum ndp_action { NDP_A_DEFAULT, NDP_A_GET, /* Show a single NDP entry */ NDP_A_GET_ALL, /* Show NDP entries */ NDP_A_GET_FOREVER, /* Repeatedly show entries */ NDP_A_DELETE, /* Delete an NDP entry */ NDP_A_SET_NCE, /* Set NDP entry */ NDP_A_SET_FILE /* Read in & set NDP entries */ }; typedef int (ndp_addr_f)(int, struct lifreq *, void *); typedef void (ndp_void_f)(void); static void ndp_usage(const char *, ...); static void ndp_fatal(const char *, ...); static void ndp_badflag(enum ndp_action); static void ndp_missingarg(char); static void ndp_run_in_child(ndp_void_f *); static void ndp_do_run(void); static void ndp_setup_handler(sigset_t *); static void ndp_start_timer(time_t period); static void ndp_run_periodically(time_t, ndp_void_f *); static int ndp_salen(const struct sockaddr *sa); static int ndp_extract_sockaddrs(struct rt_msghdr *, struct sockaddr **, struct sockaddr **, struct sockaddr **, struct sockaddr **, struct sockaddr_dl **); static int ndp_rtmsg_get(int, rtmsg_pkt_t *, struct sockaddr *); static int ndp_find_interface(int, struct sockaddr *, char *, int); static int ndp_initialize_lifreq(int, struct lifreq *, struct sockaddr *); static int ndp_host_enumerate(char *, ndp_addr_f *, void *); static int ndp_display(struct lifreq *); static int ndp_display_missing(struct lifreq *); static void ndp_lifr2ip(struct lifreq *, char *, int); static int ndp_get(int, struct lifreq *, void *); static void ndp_get_all(void); static int ndp_delete(int, struct lifreq *, void *); static int ndp_set(int, struct lifreq *, void *); static int ndp_set_nce(char *, char *, char *[], int); static int ndp_set_file(char *); static char *ndp_iface = NULL; static char *netstat_path = "/usr/bin/netstat"; static pid_t ndp_pid; static boolean_t ndp_noresolve = B_FALSE; /* Don't lookup addresses */ static boolean_t ndp_run = B_TRUE; #define MAX_ATTEMPTS 5 #define MAX_OPTS 5 #define WORDSEPS " \t\r\n" /* * Macros borrowed from route(8) for working with PF_ROUTE messages */ #define RT_ADVANCE(x, n) ((x) += ndp_salen(n)) #define RT_NEXTADDR(cp, w, u) \ l = ndp_salen(u); \ (void) memmove(cp, u, l); \ cp += l; /* * Print an error to stderr and then exit non-zero. */ static void ndp_fatal(const char *format, ...) { va_list ap; va_start(ap, format); vwarnx(format, ap); va_end(ap); exit(EXIT_FAILURE); } /* * Print out the command usage to stderr, along with any reason why it's being * printed, and then exit non-zero. */ static void ndp_usage(const char *reason, ...) { va_list ap; const char *ndp_progname = getprogname(); if (reason != NULL) { va_start(ap, reason); (void) fprintf(stderr, "%s: ", ndp_progname); (void) vfprintf(stderr, reason, ap); (void) fprintf(stderr, "\n"); va_end(ap); } (void) fprintf(stderr, "Usage: %s [-n] [-i iface] hostname\n" " %s [-n] [-i iface] -s nodeaddr etheraddr [temp] [proxy]\n" " %s [-n] [-i iface] -d nodeaddr\n" " %s [-n] [-i iface] -f filename\n" " %s [-n] -a\n" " %s [-n] -A period\n", ndp_progname, ndp_progname, ndp_progname, ndp_progname, ndp_progname, ndp_progname); exit(EXIT_FAILURE); } static void ndp_badflag(enum ndp_action action) { switch (action) { case NDP_A_DEFAULT: case NDP_A_GET: ndp_usage("Already going to print an entry, " "but extra -%c given", optopt); break; case NDP_A_GET_ALL: ndp_usage("Already going to print all entries (-a), " "but extra -%c given", optopt); break; case NDP_A_GET_FOREVER: ndp_usage("Already going to repeatedly print all entries (-A), " "but extra -%c given", optopt); break; case NDP_A_DELETE: ndp_usage("Already going to delete an entry (-d), " "but extra -%c given", optopt); break; case NDP_A_SET_NCE: ndp_usage("Already going to set an entry (-s), " "but extra -%c given", optopt); break; case NDP_A_SET_FILE: ndp_usage("Already going to set from file (-f), " "but extra -%c given", optopt); break; } } static void ndp_missingarg(char flag) { switch (flag) { case 'A': ndp_usage("Missing time period after -%c", flag); break; case 'd': ndp_usage("Missing node name after -%c", flag); break; case 'f': ndp_usage("Missing filename after -%c", flag); break; case 's': ndp_usage("Missing node name after -%c", flag); break; case 'i': ndp_usage("Missing interface name after -%c", flag); break; default: ndp_usage("Missing option argument after -%c", flag); break; } } /* * Run a function that's going to exec in a child process, and don't return * until it exits. */ static void ndp_run_in_child(ndp_void_f *func) { pid_t child_pid; int childstat = 0, status = 0; child_pid = fork(); if (child_pid == (pid_t)-1) { ndp_fatal("Unable to fork: %s", strerror(errno)); } else if (child_pid == (pid_t)0) { func(); exit(EXIT_FAILURE); } while (waitpid(child_pid, &childstat, 0) == -1) { if (errno == EINTR) continue; ndp_fatal("Failed to wait on child: %s", strerror(errno)); } status = WEXITSTATUS(childstat); if (status != 0) { ndp_fatal("Child process exited with %d", status); } } /* * SIGALRM handler to schedule a run. */ static void ndp_do_run(void) { ndp_run = B_TRUE; } /* * Prepare signal masks, and install the SIGALRM handler. Return old signal * masks through the first argument. */ static void ndp_setup_handler(sigset_t *oset) { struct sigaction sa; /* * Mask off SIGALRM so we only trigger the handler when we're ready * using sigsuspend(3C), in case the child process takes longer to * run than the alarm interval. */ if (sigprocmask(0, NULL, oset) != 0) { ndp_fatal("Unable to set signal mask: %s", strerror(errno)); } if (sighold(SIGALRM) != 0) { ndp_fatal("Unable to add SIGALRM to signal mask: %s", strerror(errno)); } sa.sa_flags = 0; sa.sa_handler = ndp_do_run; if (sigemptyset(&sa.sa_mask) != 0) { ndp_fatal("Unable to prepare empty signal set: %s", strerror(errno)); } if (sigaction(SIGALRM, &sa, NULL) != 0) { ndp_fatal("Unable to install timer handler: %s", strerror(errno)); } } /* * Start the printing timer. */ static void ndp_start_timer(time_t period) { timer_t timer; struct itimerspec interval; interval.it_value.tv_sec = interval.it_interval.tv_sec = period; interval.it_value.tv_nsec = interval.it_interval.tv_nsec = 0; if (timer_create(CLOCK_REALTIME, NULL, &timer) != 0) { ndp_fatal("Unable to create timer: %s", strerror(errno)); } if (timer_settime(timer, 0, &interval, NULL) != 0) { ndp_fatal("Unable to set time on timer: %s", strerror(errno)); } } /* * Run a given function forever periodically in a child process. */ static void ndp_run_periodically(time_t period, ndp_void_f *func) { sigset_t oset; ndp_setup_handler(&oset); ndp_start_timer(period); do { if (ndp_run) { ndp_run = B_FALSE; ndp_run_in_child(func); } (void) sigsuspend(&oset); } while (errno == EINTR); /* * Only an EFAULT should get us here. Abort so we get a core dump. */ warnx("Failure while waiting on timer: %s", strerror(errno)); abort(); } /* * Given an address, return its size. */ static int ndp_salen(const struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: return (sizeof (struct sockaddr_in)); case AF_LINK: return (sizeof (struct sockaddr_dl)); case AF_INET6: return (sizeof (struct sockaddr_in6)); default: warnx("Unrecognized sockaddr with address family %d!", sa->sa_family); abort(); } /*NOTREACHED*/ } /* * Extract all socket addresses from a routing message, and return them * through the pointers given as arguments to ndp_extract_sockaddrs. None * of the pointers should be null. */ static int ndp_extract_sockaddrs(struct rt_msghdr *rtm, struct sockaddr **dst, struct sockaddr **gate, struct sockaddr **mask, struct sockaddr **src, struct sockaddr_dl **ifp) { struct sockaddr *sa; char *cp; int i; if (rtm->rtm_version != RTM_VERSION) { warnx("Routing message version %d not understood", rtm->rtm_version); return (-1); } if (rtm->rtm_errno != 0) { warnx("Routing message couldn't be processed: %s", strerror(rtm->rtm_errno)); return (-1); } cp = ((char *)(rtm + 1)); if (rtm->rtm_addrs != 0) { for (i = 1; i != 0; i <<= 1) { if ((i & rtm->rtm_addrs) == 0) continue; /*LINTED*/ sa = (struct sockaddr *)cp; switch (i) { case RTA_DST: *dst = sa; break; case RTA_GATEWAY: *gate = sa; break; case RTA_NETMASK: *mask = sa; break; case RTA_IFP: if (sa->sa_family == AF_LINK && ((struct sockaddr_dl *)sa)->sdl_nlen != 0) *ifp = (struct sockaddr_dl *)sa; break; case RTA_SRC: *src = sa; break; } RT_ADVANCE(cp, sa); } } return (0); } /* * Given an IPv6 address, use routing information to look up * the destination and interface it would pass through. */ static int ndp_rtmsg_get(int fd, rtmsg_pkt_t *msg, struct sockaddr *sin6p) { static int seq = 0; struct sockaddr_dl sdl; int mlen, l; char ipaddr[INET6_ADDRSTRLEN]; char *cp = msg->m_space; struct rt_msghdr *m_rtm = &msg->m_rtm; bzero(msg, sizeof (rtmsg_pkt_t)); bzero(&sdl, sizeof (struct sockaddr_dl)); m_rtm->rtm_type = RTM_GET; m_rtm->rtm_version = RTM_VERSION; m_rtm->rtm_seq = ++seq; m_rtm->rtm_addrs = RTA_DST | RTA_IFP; m_rtm->rtm_msglen = sizeof (rtmsg_pkt_t); /* Place the address we're looking up after the header */ RT_NEXTADDR(cp, RTA_DST, sin6p); /* Load an empty link-level address, so we get an interface back */ sdl.sdl_family = AF_LINK; RT_NEXTADDR(cp, RTA_IFP, (struct sockaddr *)&sdl); m_rtm->rtm_msglen = cp - (char *)msg; if ((mlen = write(fd, (char *)msg, m_rtm->rtm_msglen)) < 0) { if (errno == ESRCH) { /*LINTED*/ if (inet_ntop(AF_INET6, &((sin6_t *)sin6p)->sin6_addr, ipaddr, sizeof (ipaddr)) == NULL) { (void) snprintf(ipaddr, sizeof (ipaddr), "(failed to format IP)"); }; warnx("An appropriate interface for the address %s " "is not in the routing table; use -i to force an " "interface", ipaddr); return (-1); } else { warnx("Failed to send routing message: %s", strerror(errno)); return (-1); } } else if (mlen < (int)m_rtm->rtm_msglen) { warnx("Failed to write all bytes to routing socket"); return (-1); } /* * Keep reading routing messages until we find the response to the one * we just sent. Note that we depend on the sequence number being unique * to the running program. */ do { mlen = read(fd, (char *)msg, sizeof (rtmsg_pkt_t)); } while (mlen > 0 && (m_rtm->rtm_seq != seq || m_rtm->rtm_pid != ndp_pid)); if (mlen < 0) { warnx("Failed to read from routing socket: %s", strerror(errno)); return (-1); } return (0); } /* * Find the interface that the IPv6 address would be routed through, and store * the name of the interface in the buffer passed in. */ static int ndp_find_interface(int fd, struct sockaddr *sin6p, char *buf, int buflen) { struct sockaddr *dst = NULL, *gate = NULL, *mask = NULL, *src = NULL; struct sockaddr_dl *ifp = NULL; rtmsg_pkt_t msg; if (ndp_rtmsg_get(fd, &msg, sin6p) != 0) { return (-1); } if (ndp_extract_sockaddrs(&msg.m_rtm, &dst, &gate, &mask, &src, &ifp) != 0) { return (-1); } if (ifp == NULL) { warnx("Unable to find appropriate interface for address"); return (-1); } else { if (ifp->sdl_nlen >= buflen) { warnx("The interface name \"%.*s\" is too big for the " "available buffer", ifp->sdl_nlen, ifp->sdl_data); return (-1); } else { (void) snprintf(buf, buflen, "%.*s", ifp->sdl_nlen, ifp->sdl_data); } } return (0); } /* * Zero out a lifreq struct for a SIOCLIF*ND ioctl, set the address, and fetch * the appropriate interface using the given routing socket. */ static int ndp_initialize_lifreq(int route, struct lifreq *lifrp, struct sockaddr *sap) { struct sockaddr_storage *lnr_addr; /* LINTED E_BAD_PTR_CAST_ALIGN */ struct sockaddr_in6 *sin6p = (sin6_t *)sap; char *lifr_name = lifrp->lifr_name; bzero(lifrp, sizeof (struct lifreq)); lnr_addr = &lifrp->lifr_nd.lnr_addr; if (ndp_iface != NULL) { (void) strlcpy(lifr_name, ndp_iface, LIFNAMSIZ); } else if (sin6p->sin6_scope_id != 0) { int zone_id = sin6p->sin6_scope_id; if (if_indextoname(zone_id, lifr_name) == NULL) { warnx("Invalid zone identifier: %d", zone_id); return (-1); } } else if (IN6_IS_ADDR_LINKSCOPE(&sin6p->sin6_addr)) { warnx("Link-scope addresses should specify an interface with " "a zone ID, or with -i."); return (-1); } else { if (ndp_find_interface(route, sap, lifr_name, LIFNAMSIZ) != 0) return (-1); } (void) memcpy(lnr_addr, sap, sizeof (struct sockaddr_storage)); return (0); } /* * Take a host identifier, find the corresponding IPv6 addresses and then pass * them to the specified function, along with any desired data. */ static int ndp_host_enumerate(char *host, ndp_addr_f *addr_func, void *data) { struct lifreq lifr; struct addrinfo hints, *serverinfo, *p; int err, attempts = 0; int inet6, route; bzero(&hints, sizeof (struct addrinfo)); hints.ai_family = AF_INET6; hints.ai_protocol = IPPROTO_IPV6; while (attempts < MAX_ATTEMPTS) { err = getaddrinfo(host, NULL, &hints, &serverinfo); if (err == 0) { break; } else if (err == EAI_AGAIN) { attempts++; } else { warnx("Unable to lookup %s: %s", host, gai_strerror(err)); return (-1); } } if (attempts == MAX_ATTEMPTS) { warnx("Failed multiple times to lookup %s", host); return (-1); } inet6 = socket(PF_INET6, SOCK_DGRAM, 0); if (inet6 < 0) { warnx("Failed to open IPv6 socket: %s", strerror(errno)); err = -1; } route = socket(PF_ROUTE, SOCK_RAW, 0); if (route < 0) { warnx("Failed to open routing socket: %s", strerror(errno)); err = -1; } if (err == 0) { for (p = serverinfo; p != NULL; p = p->ai_next) { if (ndp_initialize_lifreq(route, &lifr, p->ai_addr) != 0) { err = -1; continue; } if (addr_func(inet6, &lifr, data) != 0) { err = -1; continue; } } } if (close(route) != 0) { warnx("Failed to close routing socket: %s", strerror(errno)); err = -1; } if (close(inet6) != 0) { warnx("Failed to close IPv6 socket: %s", strerror(errno)); err = -1; } /* Clean up linked list */ freeaddrinfo(serverinfo); return (err); } static int ndp_display(struct lifreq *lifrp) { struct sockaddr_in6 *lnr_addr; char ipaddr[INET6_ADDRSTRLEN]; char *lladdr = NULL; char hostname[NI_MAXHOST]; int flags, gni_flags; lnr_addr = (struct sockaddr_in6 *)&lifrp->lifr_nd.lnr_addr; flags = lifrp->lifr_nd.lnr_flags; if (inet_ntop(AF_INET6, &lnr_addr->sin6_addr, ipaddr, sizeof (ipaddr)) == NULL) { warnx("Couldn't convert IPv6 address to string: %s", strerror(errno)); return (-1); }; if ((lladdr = _link_ntoa((uchar_t *)lifrp->lifr_nd.lnr_hdw_addr, NULL, lifrp->lifr_nd.lnr_hdw_len, IFT_ETHER)) == NULL) { warnx("Couldn't convert link-layer address to string: %s", strerror(errno)); return (-1); } gni_flags = ndp_noresolve ? NI_NUMERICHOST : 0; if (getnameinfo((struct sockaddr *)lnr_addr, sizeof (sin6_t), hostname, sizeof (hostname), NULL, 0, gni_flags) != 0) { warnx("Unable to lookup hostname for %s", ipaddr); free(lladdr); return (-1); } (void) printf("%s (%s) at %s", ipaddr, hostname, lladdr); if (flags & NDF_ISROUTER_ON) { (void) printf(" router"); } if (flags & NDF_ANYCAST_ON) { (void) printf(" any"); } if (!(flags & NDF_STATIC)) { (void) printf(" temp"); } if (flags & NDF_PROXY_ON) { (void) printf(" proxy"); } (void) printf("\n"); free(lladdr); return (0); } static int ndp_display_missing(struct lifreq *lifrp) { struct sockaddr_in6 *lnr_addr; char ipaddr[INET6_ADDRSTRLEN]; char hostname[NI_MAXHOST]; int flags = ndp_noresolve ? NI_NUMERICHOST : 0; lnr_addr = (struct sockaddr_in6 *)&lifrp->lifr_nd.lnr_addr; if (inet_ntop(AF_INET6, &lnr_addr->sin6_addr, ipaddr, sizeof (ipaddr)) == NULL) { warnx("Couldn't convert IPv6 address to string: %s", strerror(errno)); return (-1); }; if (getnameinfo((struct sockaddr *)lnr_addr, sizeof (sin6_t), hostname, sizeof (hostname), NULL, 0, flags) != 0) { warnx("Unable to lookup hostname for %s", ipaddr); return (-1); } (void) printf("%s (%s) -- no entry\n", ipaddr, hostname); return (0); } static void ndp_lifr2ip(struct lifreq *lifrp, char *ipaddr, int buflen) { sin6_t *lnr_addr = (sin6_t *)&lifrp->lifr_nd.lnr_addr; if (inet_ntop(AF_INET6, &lnr_addr->sin6_addr, ipaddr, buflen) == NULL) { (void) snprintf(ipaddr, buflen, "(failed to format IP)"); }; } /* * Perform a SIOCLIFGETND and print out information about it */ /*ARGSUSED*/ static int ndp_get(int fd, struct lifreq *lifrp, void *unused) { char ipaddr[INET6_ADDRSTRLEN]; if (ioctl(fd, SIOCLIFGETND, lifrp) < 0) { if (errno == ESRCH) { return (ndp_display_missing(lifrp)); } else { ndp_lifr2ip(lifrp, ipaddr, sizeof (ipaddr)); warnx("Couldn't lookup %s: %s", ipaddr, strerror(errno)); return (-1); } } return (ndp_display(lifrp)); } /* * Print out all NDP entries */ static void ndp_get_all(void) { (void) execl(netstat_path, "netstat", (ndp_noresolve ? "-np" : "-p"), "-f", "inet6", (char *)0); ndp_fatal("Coudn't exec %s: %s", netstat_path, strerror(errno)); } /* * Perform a SIOCLIFDELND ioctl */ /*ARGSUSED*/ static int ndp_delete(int fd, struct lifreq *lifrp, void *unused) { char ipaddr[INET6_ADDRSTRLEN]; if (ioctl(fd, SIOCLIFDELND, lifrp) < 0) { ndp_lifr2ip(lifrp, ipaddr, sizeof (ipaddr)); if (errno == ESRCH) { warnx("No entry for %s", ipaddr); return (-1); } else if (errno == EPERM) { warnx("Permission denied, " "could not delete entry for %s", ipaddr); return (-1); } else { warnx("Couldn't delete mapping for %s: %s", ipaddr, strerror(errno)); return (-1); } } return (0); } /* * Perform a SIOCLIFSETND ioctl using properties from the example structure. */ static int ndp_set(int fd, struct lifreq *lifrp, void *data) { char ipaddr[INET6_ADDRSTRLEN]; const lif_nd_req_t *nd_attrs = data; (void) memcpy(lifrp->lifr_nd.lnr_hdw_addr, nd_attrs->lnr_hdw_addr, ND_MAX_HDW_LEN); lifrp->lifr_nd.lnr_hdw_len = nd_attrs->lnr_hdw_len; lifrp->lifr_nd.lnr_flags = nd_attrs->lnr_flags; lifrp->lifr_nd.lnr_state_create = nd_attrs->lnr_state_create; lifrp->lifr_nd.lnr_state_same_lla = nd_attrs->lnr_state_same_lla; lifrp->lifr_nd.lnr_state_diff_lla = nd_attrs->lnr_state_diff_lla; if (ioctl(fd, SIOCLIFSETND, lifrp) < 0) { ndp_lifr2ip(lifrp, ipaddr, sizeof (ipaddr)); if (errno == EPERM) { warnx("Permission denied, " "could not set entry for %s", ipaddr); return (-1); } else { warnx("Failed to set mapping for %s: %s", ipaddr, strerror(errno)); return (-1); } } return (0); } /* * Given a host identifier, a link-layer address and possible options, * add/update the NDP mappings. */ static int ndp_set_nce(char *host, char *lladdr, char *opts[], int optlen) { lif_nd_req_t nd_attrs; uchar_t *ea; char *opt; int i; boolean_t temp = B_FALSE; boolean_t any = B_FALSE; boolean_t router = B_FALSE; bzero(&nd_attrs, sizeof (lif_nd_req_t)); ea = _link_aton(lladdr, &nd_attrs.lnr_hdw_len); if (ea == NULL) { warnx("Unable to parse link-layer address \"%s\"", lladdr); return (-1); } if (nd_attrs.lnr_hdw_len > sizeof (nd_attrs.lnr_hdw_addr)) { warnx("The size of the link-layer address is " "too large to set\n"); free(ea); return (-1); } (void) memcpy(nd_attrs.lnr_hdw_addr, ea, nd_attrs.lnr_hdw_len); free(ea); nd_attrs.lnr_state_create = ND_REACHABLE; nd_attrs.lnr_state_same_lla = ND_UNCHANGED; nd_attrs.lnr_state_diff_lla = ND_STALE; for (i = 0; i < optlen; i++) { opt = opts[i]; if (strcmp(opt, "temp") == 0) { temp = B_TRUE; } else if (strcmp(opt, "any") == 0) { any = B_TRUE; } else if (strcmp(opt, "router") == 0) { router = B_TRUE; } else if (strcmp(opt, "proxy") == 0) { warnx("NDP proxying is currently not supported"); return (-1); } else { warnx("Unrecognized option \"%s\"", opt); return (-1); } } if (!temp) { nd_attrs.lnr_flags |= NDF_STATIC; } if (any) { nd_attrs.lnr_flags |= NDF_ANYCAST_ON; } else { nd_attrs.lnr_flags |= NDF_ANYCAST_OFF; } if (router) { nd_attrs.lnr_flags |= NDF_ISROUTER_OFF; } else { nd_attrs.lnr_flags |= NDF_ISROUTER_OFF; } return (ndp_host_enumerate(host, ndp_set, &nd_attrs)); } /* * Read in a file and set the mappings from each line. */ static int ndp_set_file(char *filename) { char *line = NULL, *lasts = NULL, *curr; char *host, *lladdr; char *opts[MAX_OPTS]; int optlen = 0, lineno = 0; size_t cap = 0; boolean_t failed_line = B_FALSE; FILE *stream = fopen(filename, "r"); if (stream == NULL) { ndp_fatal("Error while opening file %s: %s", filename, strerror(errno)); } errno = 0; while (getline(&line, &cap, stream) != -1) { lineno++; if (line[0] == '#') continue; host = strtok_r(line, WORDSEPS, &lasts); if (host == NULL) { warnx("Line %d incomplete, skipping: " "missing host identifier", lineno); failed_line = B_TRUE; continue; } lladdr = strtok_r(NULL, WORDSEPS, &lasts); if (lladdr == NULL) { warnx("Line %d incomplete, skipping: " "missing link-layer address", lineno); failed_line = B_TRUE; continue; } for (optlen = 0; optlen < MAX_OPTS; optlen++) { curr = strtok_r(NULL, WORDSEPS, &lasts); if (curr == NULL) break; opts[optlen] = curr; } if (ndp_set_nce(host, lladdr, opts, optlen) != 0) { failed_line = B_TRUE; continue; } } free(line); if (errno != 0 || ferror(stream)) { ndp_fatal("Error while reading from file %s: %s", filename, strerror(errno)); } if (fclose(stream) != 0) { ndp_fatal("Error close file %s: %s", filename, strerror(errno)); } return (failed_line ? -1 : 0); } int main(int argc, char *argv[]) { char *flagarg = NULL, *lladdr = NULL; char **opts; char *endptr; int c, argsleft, optlen = 0, err = 0; long long period; enum ndp_action action = NDP_A_DEFAULT; setprogname(basename(argv[0])); if (argc < 2) { ndp_usage("No arguments given."); } while ((c = getopt(argc, argv, ":naA:d:f:i:s:")) != -1) { switch (c) { case 'n': ndp_noresolve = B_TRUE; break; case 'i': ndp_iface = optarg; break; case 's': if (action != NDP_A_DEFAULT) ndp_badflag(action); action = NDP_A_SET_NCE; flagarg = optarg; if ((argc - optind) < 1) { ndp_usage("Missing link-layer address after " "the node address, \"%s\"", flagarg); } lladdr = argv[optind++]; /* * Grab any following keywords up to the next flag */ opts = argv + optind; while ((argc - optind) > 0) { if (argv[optind][0] == '-') ndp_usage("Encountered \"%s\" after " "flag parsing is done", argv[optind]); optind++; optlen++; } break; case 'a': if (action != NDP_A_DEFAULT) ndp_badflag(action); action = NDP_A_GET_ALL; break; case 'A': if (action != NDP_A_DEFAULT) ndp_badflag(action); action = NDP_A_GET_FOREVER; flagarg = optarg; break; case 'd': if (action != NDP_A_DEFAULT) ndp_badflag(action); action = NDP_A_DELETE; flagarg = optarg; break; case 'f': if (action != NDP_A_DEFAULT) ndp_badflag(action); action = NDP_A_SET_FILE; flagarg = optarg; break; case ':': ndp_missingarg(optopt); break; case '?': ndp_usage("Unrecognized flag \"-%c\"", optopt); default: ndp_usage(NULL); } } argsleft = argc - optind; ndp_pid = getpid(); if (action != NDP_A_DEFAULT && argsleft != 0) { ndp_usage("Extra arguments leftover after parsing flags"); } switch (action) { case NDP_A_DEFAULT: case NDP_A_GET: if (argsleft != 1) { ndp_usage("Multiple arguments given without any flags"); } err = ndp_host_enumerate(argv[optind], ndp_get, NULL); break; case NDP_A_GET_ALL: ndp_get_all(); /*NOTREACHED*/ break; case NDP_A_GET_FOREVER: errno = 0; period = strtoll(flagarg, &endptr, 10); if ((period == 0 && errno != 0) || (endptr[0] != '\0') || (period < 0)) { ndp_usage("Given period should be a positive integer," " not \"%s\"", flagarg); } if (period > 86400) { ndp_usage("Given period should be shorter than a day;" " given \"%s\" seconds", flagarg); } ndp_run_periodically(period, ndp_get_all); /*NOTREACHED*/ break; case NDP_A_DELETE: err = ndp_host_enumerate(flagarg, ndp_delete, NULL); break; case NDP_A_SET_NCE: err = ndp_set_nce(flagarg, lladdr, opts, optlen); break; case NDP_A_SET_FILE: err = ndp_set_file(flagarg); break; } return (err == 0 ? 0 : 1); }