diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..a2c6771 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,20 @@ +bin_PROGRAMS = choparp +choparp_SOURCES = src/choparp.c +man_MANS = src/choparp.8 + +AM_TESTS_ENVIRONMENT = \ + AM_BUILDDIR='$(abs_top_builddir)'; \ + AM_SRCDIR='$(abs_top_srcdir)'; \ + AM_SHELL='$(SHELL)'; \ + export AM_BUILDDIR AM_SRCDIR AM_SHELL ; + +LOG_DRIVER = \ + env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh --comments + +TEST_EXTENSIONS = .sh + +SH_LOG_DRIVER = $(LOG_DRIVER) +SH_LOG_COMPILER = $(SHELL) + +TESTS = test/linux-ns-unshare.sh +EXTRA_DIST = $(TESTS) m4/NOTES diff --git a/README.md b/README.md index 05b3677..85f9b6a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,17 @@ list. Build instructions ------------------ -Requires libpcap. +Requires libpcap, autoconf, and automake. + +``` +autoreconf -is +./configure +make +make check +sudo make install +``` + +If autotools displease you, it is still possible to build directly. `gcc -o choparp choparp.c -lpcap` diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..de1ce2d --- /dev/null +++ b/configure.ac @@ -0,0 +1,46 @@ +# This is based on David A. Wheeler's template for configure.ac + +# Process this file with autoconf to produce a configure script. + +# Initialize autoconf. +AC_INIT([choparp], [20150613]) +# Force autoconf to be at least this version number: +AC_PREREQ([2.68]) +# Safety check - list a source file that wouldn't be in other directories: +AC_CONFIG_SRCDIR([src/choparp.c]) +# Put configuration results here, so we can easily #include them: +AC_CONFIG_HEADERS([config.h]) +# Put autotools auxiliary files in subdirectories to reduce clutter: +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([m4]) + +# Enable "automake" to simplify creating makefiles: +AM_INIT_AUTOMAKE([subdir-objects foreign -Wall -Werror]) +AC_CONFIG_FILES([Makefile]) + +# TAP test driver support +AC_PROG_AWK +AC_REQUIRE_AUX_FILE([tap-driver.sh]) + +# Checks for programs, e.g., AC_PROG_CC +AC_PROG_CC +# only needef for automake < 1.14 +AM_PROG_CC_C_O + +# Checks for libraries. +AC_CHECK_LIB([pcap], [pcap_lookupdev], [], [AC_MSG_ERROR([missing required library libpcap])]) + +# Checks for header files. +AC_CHECK_HEADERS([arpa/inet.h fcntl.h netinet/in.h stdlib.h string.h sys/ioctl.h sys/socket.h unistd.h], + [], [AC_MSG_ERROR([missing required platform headers])]) +AC_CHECK_HEADER([pcap.h], [], [AC_MSG_ERROR([missing required header pcap.h])]) + +# Checks for typedefs, structures, and compiler characteristics. + +# Checks for library functions. +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_CHECK_FUNCS([ftruncate inet_ntop memset socket strchr strtol]) + +# Do final output. +AC_OUTPUT diff --git a/m4/NOTES b/m4/NOTES new file mode 100644 index 0000000..60f99fe --- /dev/null +++ b/m4/NOTES @@ -0,0 +1,6 @@ +This directory was added to conform with modern autotools conventions, +superseding acinclude.m4. Additional local Autoconf macros would +reside here. + +This NOTES file exists to add the directory to git and effectively +suppresses a spurious warning message. diff --git a/src/choparp.8 b/src/choparp.8 index 3921ceb..1df1c9e 100644 --- a/src/choparp.8 +++ b/src/choparp.8 @@ -35,13 +35,15 @@ .Nd cheap and omitted proxy ARP .Sh SYNOPSIS .Nm chpoarp +.Op Fl p Ar file +.Op Fl v .Ar if_name mac_addr .Oo Fl Oc Ns Ar net_addr Ns .Op / Ns Ar net_mask .Ar ... .Sh DESCRIPTION .Pp -.Nm choparp +.Nm choparp is a easy-to-use proxy ARP daemon. It watches ARP request packets visible on the interface specified by argument .Ar if_name , @@ -49,61 +51,85 @@ and sends proxy ARP reply to the sender if the ARP request queries the MAC address .Pq ethernet hardware address for the network specified by -.Ar net_addr Ns / Ar net_mask . +.Ar net_addr Ns / Ns Ar net_mask . .Pp +.Bl -tag -width indent +.It Fl p Ar file +Save process id to the given file. +.It Fl v +Send diagnostic messages to stdout. Repeat to increase verbosity. +.It Ar if_name +Network interface on which to listen and respond to ARP requests. +.It Ar mac_addr +The MAC address to be published for the specified hosts, in one of three formats. +.Bl -tag -width indent +.It Ar xx:xx:xx:xx:xx:xx +Static value specified as 6 colon-separated byte values in hexadecimal. +.It Ar vhid: Ns Oo |0x Oc +Virtual router (VRRP/CARP) address with the final octet (VRID) +given by decimal value or hexadecimal value . +.It Ar auto +Usually, .Ar mac_addr -is th MAC address to be published for the specified hosts. -It is normally the address of +is the address of .Ar if_name . -The format of -.Ar mac_addr -must be 6 colon-separated bytes of hexadecimal value, such as -.Ad 00:00:01:01:14:46 . -The keyword -.Ad auto -can also be used to use the address of +Specify the keyword +.Sy auto +to use the address of .Ar if_name -from the system configuration. -.Pp -A -.Xr carp 4 -virtual router address can be specified as -.Ad vhid: +detected from the system configuration. +.El . -.Pp -.Ar net_addr -must be in dotted quad notation -.Pq for example Ad 133.138.1.134 -or be a 32 bit hexadecimal value starting with +.It Ar net_addr +IPv4 address in either dotted quad notation +.Pq for example Ar 192.0.2.2 +or, BSD-style, as a 32 bit hexadecimal value starting with .Dq 0x -.Pq for example Ad 0x858a0186 . +.Pq for example Ar 0x858a0186 . +.It Ar net_mask +Alone, +.Ar net_addr +specifies a single host; to specify an IP range, include a .Ar net_mask -can likewise be speficied as a dotted quad or hexadecimal value, -or alternatively as a mask length. The following address specifications -are therefore equivalent: +in the same way one would specify a subnet. +Full 32-bit values may be in either dotted-decimal and BSD-style hexadecimal format. +Prefix lengths may be specified in CIDR notation. +The following address specifications are equivalent: .Bl -item -offset indent .It -.Ad 192.168.98.0/255.255.254.0 +.Ar 192.0.2.0/255.255.254.0 .It -.Ad 192.168.98.0/0xfffffe00 +.Ar 0xc0000200/0xfffffe00 .It -.Ad 192.168.98.0/23 +.Ar 192.0.2.0/0xfffffe00 +.It +.Ar 192.0.2.0/23 .El .Pp -Multiple addresses can be specified. -Addresses can be -.Em excluded -by preceding them with -.Fl +Address matches may be either affirmative or negative, with affirmative as the default. +To exclude an address or range, specify a negative match with a prefix +.Dq - +(dash) prefix. +Multiple matches may be specified, with at least one being affirmative. +.El .Sh EXAMPLES If you have network interface .Dq ne0 with MAC address -.Dq 00:00:01:01:14:16 , -and would like to send proxy ARP reply for 192.168.0.64/26, +.Dq 00:00:5e:00:53:01 +, and would like to send proxy ARP reply for 192.168.0.64/26, the argument would be as follows: .Bd -literal -offset indent -choparp ne0 00:00:01:01:14:46 192.168.0.64/255.255.255.192 +choparp ne0 00:00:5e:00:53:01 192.168.0.64/26 +.Ed +.Pp +Were +.Dq ne0 +assigned the IPv4 address 192.168.0.65/26, +you could additionally exclude the the host address and all-ones and all-zeroes +broadcast addresses as follows: +.Bd -literal -offset indent +choparp ne0 00:00:5e:00:53:01 192.168.0.64/26 -192.168.0.64/31 -192.168.0.127 .Ed .Sh BUGS Supports Ethernet interfaces only. diff --git a/src/choparp.c b/src/choparp.c index b284583..43e8078 100644 --- a/src/choparp.c +++ b/src/choparp.c @@ -52,6 +52,7 @@ #include #include #include +#include #ifndef __linux__ #include @@ -73,39 +74,29 @@ char errbuf[PCAP_ERRBUF_SIZE]; u_char target_mac[ETHER_ADDR_LEN]; /* target MAC address */ static char *pidfile = NULL; +static int verbose = 0; static pcap_t *pc; char* cidr_to_str(struct cidr *a) { - char buf[64]; - char *res = NULL; - int res_alloc, res_len; - int len; + char addr[INET_ADDRSTRLEN], mask[INET_ADDRSTRLEN]; + char *res = NULL, *oldres = NULL; + char *prefix = ""; + char *sep = ""; while (a) { - if (a->mask.s_addr == INADDR_NONE) { - len = snprintf(buf, sizeof buf, "dst host %s", inet_ntoa(a->addr)); - } else { - len = snprintf(buf, sizeof buf, "dst net %s mask ", inet_ntoa(a->addr)); - len += snprintf(buf + len, sizeof buf - len, "%s", inet_ntoa(a->mask)); - } - - if (!res) { - res_alloc = 1024; - res = malloc(res_alloc); - strncpy(res, buf, res_alloc); - res_len = len; - - } else { - if (res_len + len + 5 > res_alloc) { - res_alloc *= 2; - res = realloc(res, res_alloc); - } - strncat(res, " or ", res_alloc - res_len - 1); - res_len += 4; - strncat(res, buf, res_alloc - res_len - 1); - res_len += len; + inet_ntop(AF_INET, &a->addr, addr, sizeof(addr)); + inet_ntop(AF_INET, &a->mask, mask, sizeof(mask)); + if (a->mask.s_addr == INADDR_NONE + ? asprintf(&res, "%s%sdst host %s", prefix, sep, addr) < 0 + : asprintf(&res, "%s%sdst net %s mask %s", prefix, sep, addr, mask) < 0 + ) { + perror("asprintf"); + exit(1); } + free(oldres); + prefix = oldres = res; + sep = " or "; a = a->next; } return res; @@ -115,6 +106,7 @@ pcap_t * open_pcap(char *ifname, char *filter_str) { pcap_t *pc = NULL; struct bpf_program filter; + int dlt; /* Set up PCAP */ if ((pc = pcap_open_live(ifname, 128, 0, 512, errbuf))==NULL){ @@ -134,6 +126,12 @@ open_pcap(char *ifname, char *filter_str) { exit(1); } + /* wired ethernet only */ + if ((dlt = pcap_datalink(pc)) != DLT_EN10MB) { + fprintf(stderr, "unsupported link type: %s\n", pcap_datalink_val_to_name(dlt)); + exit(1); + } + pcap_freecode(&filter); return pc; } @@ -158,13 +156,36 @@ gen_arpreply(u_char *buf) { } void -cleanup(int sig){ - if (pidfile != NULL) - unlink(pidfile); +cleanup_pidfile() { + if (pidfile != NULL) + unlink(pidfile); +} + +void +breakloop_pc(int sig) { + pcap_breakloop(pc); +} - pcap_breakloop(pc); - pcap_close(pc); - exit(0); +void +setup_pidfile() { + int fd; + FILE *fp; + if (pidfile == NULL) + return; + + /* errno may or may not be set to something useful */ + errno = 0; + if ( + (fd = open(pidfile, O_WRONLY | O_CREAT | O_SYNC, 0600)) < 0 + || atexit(cleanup_pidfile) < 0 + || ftruncate(fd, 0) < 0 + || (fp = fdopen(fd, "w")) == NULL + || fprintf(fp, "%ld\n", (long)getpid()) < 0 + || fclose(fp) == EOF + ) { + perror("failed to create pidfile"); + exit(1); + } } void @@ -173,32 +194,41 @@ process_arp(u_char *user, const struct pcap_pkthdr *pkthdr, const u_char *packet pcap_inject((pcap_t *)user, packet, pkthdr->len); } -int +void setmac(char *addr, char *ifname){ - u_int m0, m1, m2, m3, m4, m5; + int len = 0; + unsigned m0, m1, m2, m3, m4, m5; if (!strcmp (addr, "auto")) { #ifdef __linux__ int fd; struct ifreq ifr; + if (strlen(ifname) + 1 > sizeof(ifr.ifr_name)) { + fprintf(stderr, "if_name too long: %s\n", ifname); + exit(1); + } + strcpy(ifr.ifr_name, ifname); + if ((fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); - return -1; + exit(1); } - strncpy(ifr.ifr_name, ifname, sizeof ifr.ifr_name); - if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { perror("ioctl(SIOCGIFHWADDR)"); - return -1; + exit(1); } memcpy(target_mac, ifr.ifr_hwaddr.sa_data, ETHER_ADDR_LEN); - return 0; + close(fd); + return; #else struct ifaddrs *ifas, *ifa; - getifaddrs (&ifas); + if(getifaddrs(&ifas) < 0) { + perror("getifaddrs"); + exit(1); + } for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { #define SDL ((struct sockaddr_dl *)ifa->ifa_addr) if (strcmp (ifa->ifa_name, ifname) @@ -206,29 +236,39 @@ setmac(char *addr, char *ifname){ || SDL->sdl_alen != ETHER_ADDR_LEN) continue; memcpy (target_mac, SDL->sdl_data + SDL->sdl_nlen, ETHER_ADDR_LEN); - return 0; + freeifaddrs(ifas); + return; } -#endif fprintf(stderr, "%s: not found\n", ifname); - return -1; - - } else if (!strncmp (addr, "vhid:", 4)) { + exit(2); +#endif + } + if (sscanf(addr, "vhid:%n", &len) >= 0 && len) { /* * Virtual router mac address * CARP address format: 00:00:5e:00:01: */ - char *vhid = addr + 5; - if (!*vhid) - return(-1); - m0 = 0; - m1 = 0; - m2 = 0x5e; - m3 = 0; - m4 = 1; - m5 = atoi(vhid); - } else if (sscanf(addr, "%x:%x:%x:%x:%x:%x", &m0, &m1, &m2, &m3, &m4, &m5) < 6) { + unsigned vhid; + /* allow decimal and hexadecimal (leading zero is decimal, not octal) */ + int matched = + sscanf(addr, "vhid:0x%x%n", &vhid, &len) > 0 + || sscanf(addr, "vhid:%u%n", &vhid, &len) > 0; + if (!matched || addr[len] || vhid > 0xff) { + fprintf(stderr, "invalid vhid spec: %s\n", addr); + exit(2); + } + target_mac[0] = 0; + target_mac[1] = 0; + target_mac[2] = 0x5e; + target_mac[3] = 0; + target_mac[4] = 1; + target_mac[5] = (u_char)vhid; + return; + } + if (sscanf(addr, "%2x:%2x:%2x:%2x:%2x:%2x%n", &m0, &m1, &m2, &m3, &m4, &m5, &len) < 6 + || addr[len]) { fprintf(stderr, "invalid MAC address: %s", addr); - return(-1); + exit(2); } target_mac[0] = (u_char )m0; target_mac[1] = (u_char )m1; @@ -236,32 +276,44 @@ setmac(char *addr, char *ifname){ target_mac[3] = (u_char )m3; target_mac[4] = (u_char )m4; target_mac[5] = (u_char )m5; - return(0); } int atoip(char *buf, u_int32_t *ip_addr){ - u_int i0, i1, i2, i3; - - if (sscanf(buf, "%u.%u.%u.%u", &i0, &i1, &i2, &i3) == 4){ - *ip_addr = (i0 << 24) + (i1 << 16) + (i2 << 8) + i3; - return(0); + unsigned i0, i1, i2, i3; + unsigned long hex_addr; + int len = 0; + + if (sscanf(buf, "%u.%u.%u.%u%n", &i0, &i1, &i2, &i3, &len) >= 4 + && i0 <= 0xff + && i1 <= 0xff + && i2 <= 0xff + && i3 <= 0xff + && !buf[len] + ) { + *ip_addr = (i0 << 24) + (i1 << 16) + (i2 << 8) + i3; + return 0; + } + if (sscanf(buf, "0x%lx%n", &hex_addr, &len) >= 1 + && hex_addr <= 0xffffffff + && !buf[len] + ) { + *ip_addr = (u_int32_t)hex_addr; + return 0; } - if (sscanf(buf, "0x%lx", (unsigned long *) ip_addr) == 1) - return(0); - return(-1); + return -1; } void usage(void){ fprintf(stderr,"usage: choparp [-p PIDFILE] if_name mac_addr [-]addr/mask...\n"); - exit(-1); + exit(2); } int main(int argc, char **argv){ - int pidf, opt; + int opt; char *ifname; char *filter, *targets_filter, *excludes_filter; struct cidr **targets_tail = &targets, **excludes_tail = &excludes; @@ -276,31 +328,28 @@ main(int argc, char **argv){ (LIST ## _tail) = &(*(LIST ## _tail))->next; \ } while (0) - while ((opt = getopt(argc, argv, "p:")) != -1) { + while ((opt = getopt(argc, argv, "+p:vh")) != -1) { switch (opt) { case 'p': pidfile = optarg; break; - case '?': + case 'v': + ++verbose; + break; + case 'h': + default: usage(); } } - if (pidfile == NULL) { - argv++; - argc--; - } else { - argv += 3; - argc -= 3; - } + argv += optind; + argc -= optind; if (argc < 3) usage(); ifname = argv[0]; - if (setmac(argv[1], ifname)) { - exit(1); - } + setmac(argv[1], ifname); argv += 2; argc -= 2; while (argc > 0) { @@ -332,7 +381,6 @@ main(int argc, char **argv){ argv++, argc--; } -#ifdef DEBUG #define SHOW(LIST) \ do { \ struct cidr *t; \ @@ -343,10 +391,10 @@ main(int argc, char **argv){ } \ } while (0) - SHOW(targets); - SHOW(excludes); - exit (0); -#endif + if (verbose >= 3) { + SHOW(targets); + SHOW(excludes); + } targets_filter = cidr_to_str(targets); excludes_filter = cidr_to_str(excludes); @@ -358,31 +406,37 @@ main(int argc, char **argv){ "and (%s)" #define EXCL_FILTER TMPL_FILTER " and not (%s)" - if (excludes_filter == NULL) - asprintf (&filter, TMPL_FILTER, targets_filter); - else - asprintf (&filter, EXCL_FILTER, targets_filter, excludes_filter); + if (targets_filter == NULL) { + fprintf(stderr, "at least one address range must be an affirmative match\n"); + exit(1); + } + if (excludes_filter == NULL + ? asprintf(&filter, TMPL_FILTER, targets_filter) < 0 + : asprintf(&filter, EXCL_FILTER, targets_filter, excludes_filter) < 0 + ) { + perror("asprintf"); + exit(1); + } + free(targets_filter); + free(excludes_filter); -#ifdef DEBUG - fprintf(stderr, "Filter on %s: %s\n", ifname, filter); -#endif - if ((pc = open_pcap(ifname, filter)) < 0) - exit(1); + if (verbose) + printf("Filter on %s: %s\n", ifname, filter); + + pc = open_pcap(ifname, filter); free(filter); - if (pidfile != NULL) { - pidf = open(pidfile, O_RDWR | O_CREAT | O_FSYNC, 0600); - if (pidf > 0) { - ftruncate(pidf, 0); - dprintf(pidf, "%u\n", getpid()); - close(pidf); - memset(&act, 0, sizeof(act)); - act.sa_handler = cleanup; - sigaction(SIGINT, &act, NULL); - sigaction(SIGTERM, &act, NULL); - } - } + setup_pidfile(); + + memset(&act, 0, sizeof(act)); + act.sa_handler = breakloop_pc; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); - pcap_loop(pc, 0, process_arp, (u_char*)pc); - exit(1); + if (pcap_loop(pc, 0, process_arp, (u_char*)pc) == -1) { + pcap_perror(pc, "pcap_loop"); + exit(1); + } + pcap_close(pc); + exit(0); } diff --git a/test/linux-ns-unshare-stage2.sh b/test/linux-ns-unshare-stage2.sh new file mode 100644 index 0000000..aff7a95 --- /dev/null +++ b/test/linux-ns-unshare-stage2.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# Verify the sandbox created in stage1 and proceed with functional tests. +# + +STAGE2_NETNS=$(readlink /proc/$$/ns/net | grep -E '^net:\[[0-9]+\]') + +if [ -z "$STAGE1_NETNS" ] || -z [ "$STAGE2_NETNS" ] || + [ "$STAGE1_NETNS" '=' "$STAGE2_NETNS" ] +then + echo "Bail out! Failed to confirm sandbox" + exit +fi + +. "$AM_SRCDIR/test/linux-veth.sh" diff --git a/test/linux-ns-unshare.sh b/test/linux-ns-unshare.sh new file mode 100644 index 0000000..2a5b1ba --- /dev/null +++ b/test/linux-ns-unshare.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# Meaningful tests require the freedom to break things. Linux namespaces can +# give us a disposable sandbox without the need for persistent changes and +# end-user configuration. If the host allows unprivileged userns, tests can even +# run without root. + +if [ -z "$AM_BUILDDIR" ] +then + echo "1..0 # Skipped: missing \$AM_BUILDDIR" + echo "# Did you run me under 'make check'?" + exit +fi + +if ! ((unshare --help | grep map-root-user) && ip link show) > /dev/null +then + echo "1..0 # Skipped: required linux utilities seem to be missing" + echo "# Test requires util-linux >= 2.25 and iproute2" + exit +fi + +if ! unshare --user --map-root-user --net true +then + echo "1..0 # Skipped: unable to create private user/network namespace" + echo "# Test requires root or kernel.unprivileged_userns_clone=1" + exit +fi + +export LC_CTYPE=C + +STAGE1_NETNS=$(readlink /proc/$$/ns/net | grep -E '^net:\[[0-9]+\]$') +export STAGE1_NETNS + +STAGE2="$AM_SRCDIR/test/linux-ns-unshare-stage2.sh" + +if ! unshare --user --map-root-user --net "$AM_SHELL" "$STAGE2" +then + echo "Bail out! Failure in second stage" + exit +fi diff --git a/test/linux-veth.sh b/test/linux-veth.sh new file mode 100644 index 0000000..f9146f0 --- /dev/null +++ b/test/linux-veth.sh @@ -0,0 +1,263 @@ +#!/bin/sh + +# +# Functional tests. Do NOT run this on a production system or any environment +# not devoted to testing. This script is normally run from linux-ns-unshare.sh +# which configures a sandbox. Docker, GitLab, autopkgtest, etc are fine too. +# Just don't run this script casually. +# + +set -x + +if [ -z "$AM_BUILDDIR" ] +then + echo "1..0 # Skipped: missing \$AM_BUILDDIR" + echo "# Did you run me under 'make check'? " + exit +fi + +if ! ip link add is-at type veth peer name who-has || + ! ip addr add 192.168.1.200/24 dev who-has || + ! ip link set is-at up || + ! ip link set who-has up +then + echo "1..0 # Skipped: unable to set up private veth interfaces" + exit +fi + +# Allow this many seconds for choparp start-up before sending test arp requests +startup_grace=1 + +# Any arbitrary value should do, but might as well switch it up between runs +rnd_byte="$(dd if=/dev/urandom bs=1 count=1 2>/dev/null | od -A n -t d)" + +lladdr() { + printf '02:00:c0:a8:%02x:%02x' "$rnd_byte" "$1" +} + +ipaddr() { + printf '192.168.%d.%d' "$rnd_byte" "$1" +} + +hex_ipaddr() { + printf '0xc0a8%02x%02x' "$rnd_byte" "$1" +} + +arp_for() { + ( + for i + do + ping -I who-has -c 1 -w 1 $(ipaddr $i) & + done + wait # for all jobs in this subshell + ) > /dev/null +} + +found() { + ip -4 neigh show dev who-has | \ + grep -i -q "$(ipaddr $1) lladdr ${2:-.*} REACHABLE" +} + +####################################################################### + +echo "1..9" + +####################################################################### + +test_desc="1 - Base case, static hardware address and single-host" +"$AM_BUILDDIR"/choparp is-at $(lladdr 1) $(ipaddr 1) & +chopid=$! +sleep $startup_grace + +arp_for 1 +kill $chopid + +if ! wait $chopid +then + echo "not ok $test_desc # abnormal exit $?" +elif ! found 1 +then + echo "not ok $test_desc # MAC resolution failure" +else + echo "ok $test_desc" +fi + +####################################################################### + +test_desc="2 - Target IP by legacy subnet" +"$AM_BUILDDIR"/choparp is-at $(lladdr 2) $(ipaddr 2)/255.255.255.254 & +chopid=$! +sleep $startup_grace + +arp_for 2 3 +kill $chopid + +if ! wait $chopid +then + echo "not ok $test_desc # abnormal exit $?" +elif ! (found 2 && found 3) +then + echo "not ok $test_desc # MAC resolution failure" +else + echo "ok $test_desc" +fi + +####################################################################### + +test_desc="3 - Target IP by BSD-style hex" +"$AM_BUILDDIR"/choparp is-at $(lladdr 4) $(hex_ipaddr 4)/0xfffffffe & +chopid=$! +sleep $startup_grace + +arp_for 4 +arp_for 5 +kill $chopid + +if ! wait $chopid +then + echo "not ok $test_desc # abnormal exit $?" +elif ! (found 4 && found 5) +then + echo "not ok $test_desc # MAC resolution failure" +else + echo "ok $test_desc" +fi + +####################################################################### + +test_desc="4 - Target IP by IP list" +"$AM_BUILDDIR"/choparp is-at $(lladdr 6) $(ipaddr 6) $(ipaddr 7) & +chopid=$! +sleep $startup_grace + +arp_for 6 7 +kill $chopid + +if ! wait $chopid +then + echo "not ok $test_desc # abnormal exit $?" +elif ! (found 6 && found 7) +then + echo "not ok $test_desc # MAC resolution failure" +else + echo "ok $test_desc" +fi + +####################################################################### + +test_desc="5 - Target IP by CIDR subnet and exclusion" +"$AM_BUILDDIR"/choparp is-at $(lladdr 8) $(ipaddr 8)/30 -$(ipaddr 10) & +chopid=$! +sleep $startup_grace + +arp_for 8 9 10 11 +kill $chopid + +if ! wait $chopid +then + echo "not ok $test_desc # abnormal exit $?" +elif ! (found 8 && found 9 && ! found 10 && found 11) +then + echo "not ok $test_desc # MAC resolution failure" +else + echo "ok $test_desc" +fi + +####################################################################### + +test_desc="6 - Hardware address detection by \"auto\" keyword" +"$AM_BUILDDIR"/choparp is-at auto $(ipaddr 12) & +chopid=$! +sleep $startup_grace + +arp_for 12 +kill $chopid + +if ! wait $chopid +then + echo "not ok $test_desc # abnormal exit $?" +elif ! found 12 +then + echo "not ok $test_desc # MAC resolution failure" +else + echo "ok $test_desc" +fi + +####################################################################### + +test_desc="7 - Hardware address by \"vhid\" keyword, decimal" +"$AM_BUILDDIR"/choparp is-at vhid:13 $(ipaddr 13) & +chopid=$! +sleep $startup_grace + +arp_for 13 +kill $chopid + +ip neigh show dev who-has + +if ! wait $chopid +then + echo "not ok $test_desc # abnormal exit $?" +elif ! found 13 00:00:5e:00:01:0d +then + echo "not ok $test_desc # MAC resolution failure" +else + echo "ok $test_desc" +fi + +####################################################################### + +test_desc="8 - Hardware address by \"vhid\" keyword, hex" +"$AM_BUILDDIR"/choparp is-at vhid:0x0e $(ipaddr 14) & +chopid=$! +sleep $startup_grace + +arp_for 14 +kill $chopid + +ip neigh show dev who-has + +if ! wait $chopid +then + echo "not ok $test_desc # abnormal exit $?" +elif ! found 14 00:00:5e:00:01:0e +then + echo "not ok $test_desc # MAC resolution failure" +else + echo "ok $test_desc" +fi + +####################################################################### + +test_desc="9 - Pidfile with -p" +pidfile=$(mktemp /tmp/choparp.pid-XXXXXXXX) +"$AM_BUILDDIR"/choparp -p $pidfile is-at auto $(ipaddr 15) & +chopid=$! +sleep $startup_grace + +chopid_from_file=$(cat "$pidfile") +arp_for 15 +kill $chopid + +if ! wait $chopid +then + echo "not ok $test_desc # abnormal exit $?" +elif ! found 15 +then + echo "not ok $test_desc # MAC resolution failure" +elif ! [ "$chopid" -eq "$chopid_from_file" ] +then + echo "not ok $test_desc # invalid pidfile" +elif [ -f "$pidfile" ] +then + echo "not ok $test_desc # pidfile not removed after exit" +else + echo "ok $test_desc" +fi + +####################################################################### + +# Cleanup +ip link delete is-at + +exit 0