diff --git a/tools/send_arp.linux.c b/tools/send_arp.linux.c index e1c1960..477100a 100644 --- a/tools/send_arp.linux.c +++ b/tools/send_arp.linux.c @@ -7,6 +7,23 @@ * 2 of the License, or (at your option) any later version. * * Authors: Alexey Kuznetsov, + * YOSHIFUJI Hideaki + */ + +/* Andrew Beekhof, Lars Ellenberg: + * Based on arping from iputils, + * adapted to the command line conventions established by the libnet based + * send_arp tool as used by the IPaddr and IPaddr2 resource agents. + * The libnet based send_arp, and its command line argument convention, + * was first added to the heartbeat project by Matt Soffen. + * + * Latest "resync" with iputils as of: + * git://git.linux-ipv6.org/gitroot/iputils.git + * 511f8356e22615479c3cc16bca64d72d204f6df3 + * Fri Jul 24 10:48:47 2015 + * To get various bugfixes and support for infiniband and other link layer + * addresses which do not fit into plain "sockaddr_ll", and broadcast addresses + * that may be different from memset(,0xff,). */ #include @@ -16,12 +33,17 @@ #include #include #include +#include #include -#include +#include #include #include #include #include +#ifdef CAPABILITIES +#include +#include +#endif #include #include @@ -32,40 +54,85 @@ #include #include -static void usage(void) __attribute__((noreturn)); +#ifdef USE_SYSFS +#include +struct sysfs_devattr_values; +#endif -static int quit_on_reply; -static char *device; -static int ifindex; -static char *source; -static struct in_addr src, dst; -static char *target; -static int dad = 0, unsolicited = 0, advert = 0; -static int quiet = 0; -static int count = -1; -static int timeout = 0; -static int unicasting = 0; -static int s = 0; -static int broadcast_only = 0; +#ifndef WITHOUT_IFADDRS +#include +#endif -static struct sockaddr_ll me; -static struct sockaddr_ll he; +#ifdef USE_IDN +#include +#include +#endif -static struct timeval start, last; +static char SNAPSHOT[] = "s20121221"; -static int sent, brd_sent; -static int received, brd_recv, req_recv; +static void usage(void) __attribute__((noreturn)); + +#ifndef DEFAULT_DEVICE +#define DEFAULT_DEVICE "eth0" +#endif +#ifdef DEFAULT_DEVICE +# define DEFAULT_DEVICE_STR DEFAULT_DEVICE +#else +# define DEFAULT_DEVICE NULL +#endif + +struct device { + const char *name; + int ifindex; +#ifndef WITHOUT_IFADDRS + struct ifaddrs *ifa; +#endif +#ifdef USE_SYSFS + struct sysfs_devattr_values *sysfs; +#endif +}; + +int quit_on_reply=0; +struct device device = { + .name = DEFAULT_DEVICE, +}; +char *source; +struct in_addr src, dst; +char *target; +int dad, unsolicited, advert; +int quiet; +int count=-1; +int timeout; +int unicasting; +int s; +int broadcast_only; + +struct sockaddr_storage me; +struct sockaddr_storage he; + +struct timeval start, last; + +int sent, brd_sent; +int received, brd_recv, req_recv; + +#ifndef CAPABILITIES +static uid_t euid; +#endif #define MS_TDIFF(tv1,tv2) ( ((tv1).tv_sec-(tv2).tv_sec)*1000 + \ ((tv1).tv_usec-(tv2).tv_usec)/1000 ) -static void print_hex(unsigned char *p, int len); -static int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM); -static void set_signal(int signo, void (*handler)(void)); -static int send_pack(int s, struct in_addr src, struct in_addr dst, - struct sockaddr_ll *ME, struct sockaddr_ll *HE); -static void finish(void); -static void catcher(void); +#define OFFSET_OF(name,ele) ((size_t)&((name *)0)->ele) + +static socklen_t sll_len(size_t halen) +{ + socklen_t len = OFFSET_OF(struct sockaddr_ll, sll_addr) + halen; + if (len < sizeof(struct sockaddr_ll)) + len = sizeof(struct sockaddr_ll); + return len; +} + +#define SLL_LEN(hln) sll_len(hln) void usage(void) { @@ -80,14 +147,18 @@ void usage(void) " -V : print version and exit\n" " -c count : how many packets to send\n" " -w timeout : how long to wait for a reply\n" - " -I device : which ethernet device to use (eth0)\n" + " -I device : which ethernet device to use" +#ifdef DEFAULT_DEVICE_STR + " (" DEFAULT_DEVICE_STR ")" +#endif + "\n" " -s source : source ip address\n" " destination : ask for what ip address\n" ); exit(2); } -void set_signal(int signo, void (*handler)(void)) +static void set_signal(int signo, void (*handler)(void)) { struct sigaction sa; @@ -97,7 +168,126 @@ void set_signal(int signo, void (*handler)(void)) sigaction(signo, &sa, NULL); } -int send_pack(int s, struct in_addr src, struct in_addr dst, +#ifdef CAPABILITIES +static const cap_value_t caps[] = { CAP_NET_RAW, }; +static cap_flag_value_t cap_raw = CAP_CLEAR; +#endif + +static void limit_capabilities(void) +{ +#ifdef CAPABILITIES + cap_t cap_p; + + cap_p = cap_get_proc(); + if (!cap_p) { + perror("arping: cap_get_proc"); + exit(-1); + } + + cap_get_flag(cap_p, CAP_NET_RAW, CAP_PERMITTED, &cap_raw); + + if (cap_raw != CAP_CLEAR) { + if (cap_clear(cap_p) < 0) { + perror("arping: cap_clear"); + exit(-1); + } + + cap_set_flag(cap_p, CAP_PERMITTED, 1, caps, CAP_SET); + + if (cap_set_proc(cap_p) < 0) { + perror("arping: cap_set_proc"); + if (errno != EPERM) + exit(-1); + } + } + + if (prctl(PR_SET_KEEPCAPS, 1) < 0) { + perror("arping: prctl"); + exit(-1); + } + + if (setuid(getuid()) < 0) { + perror("arping: setuid"); + exit(-1); + } + + if (prctl(PR_SET_KEEPCAPS, 0) < 0) { + perror("arping: prctl"); + exit(-1); + } + + cap_free(cap_p); +#else + euid = geteuid(); +#endif +} + +static int modify_capability_raw(int on) +{ +#ifdef CAPABILITIES + cap_t cap_p; + + if (cap_raw != CAP_SET) + return on ? -1 : 0; + + cap_p = cap_get_proc(); + if (!cap_p) { + perror("arping: cap_get_proc"); + return -1; + } + + cap_set_flag(cap_p, CAP_EFFECTIVE, 1, caps, on ? CAP_SET : CAP_CLEAR); + + if (cap_set_proc(cap_p) < 0) { + perror("arping: cap_set_proc"); + return -1; + } + + cap_free(cap_p); +#else + if (setuid(on ? euid : getuid())) { + perror("arping: setuid"); + return -1; + } +#endif + return 0; +} + +static int enable_capability_raw(void) +{ + return modify_capability_raw(1); +} + +static int disable_capability_raw(void) +{ + return modify_capability_raw(0); +} + +static void drop_capabilities(void) +{ +#ifdef CAPABILITIES + cap_t cap_p = cap_init(); + + if (!cap_p) { + perror("arping: cap_init"); + exit(-1); + } + + if (cap_set_proc(cap_p) < 0) { + perror("arping: cap_set_proc"); + exit(-1); + } + + cap_free(cap_p); +#else + if (setuid(getuid()) < 0) { + perror("arping: setuid"); + exit(-1); + } +#endif +} + +static int send_pack(int s, struct in_addr src, struct in_addr dst, struct sockaddr_ll *ME, struct sockaddr_ll *HE) { int err; @@ -130,7 +320,7 @@ int send_pack(int s, struct in_addr src, struct in_addr dst, p+=4; gettimeofday(&now, NULL); - err = sendto(s, buf, p-buf, 0, (struct sockaddr*)HE, sizeof(*HE)); + err = sendto(s, buf, p-buf, 0, (struct sockaddr*)HE, SLL_LEN(ah->ar_hln)); if (err == p-buf) { last = now; sent++; @@ -140,7 +330,7 @@ int send_pack(int s, struct in_addr src, struct in_addr dst, return err; } -void finish(void) +static void finish(void) { if (!quiet) { printf("Sent %d probes (%d broadcast(s))\n", sent, brd_sent); @@ -158,40 +348,43 @@ void finish(void) printf("\n"); fflush(stdout); } - - if (dad) { - fflush(stdout); - exit(!!received); - } - + fflush(stdout); + if (dad) + exit(!!received); if (unsolicited) exit(0); - - fflush(stdout); exit(!received); } -void catcher(void) +static void catcher(void) { - struct timeval tv; + struct timeval tv, tv_s, tv_o; gettimeofday(&tv, NULL); if (start.tv_sec==0) start = tv; - if (count-- == 0 || (timeout && MS_TDIFF(tv,start) > timeout*1000 + 500)) + timersub(&tv, &start, &tv_s); + tv_o.tv_sec = timeout; + tv_o.tv_usec = 500 * 1000; + + if (count-- == 0 || (timeout && timercmp(&tv_s, &tv_o, >))) finish(); - if (last.tv_sec==0 || MS_TDIFF(tv,last) > 500) { - send_pack(s, src, dst, &me, &he); + timersub(&tv, &last, &tv_s); + tv_o.tv_sec = 0; + + if (last.tv_sec==0 || timercmp(&tv_s, &tv_o, >)) { + send_pack(s, src, dst, + (struct sockaddr_ll *)&me, (struct sockaddr_ll *)&he); if (count == 0 && unsolicited) finish(); } alarm(1); } -void print_hex(unsigned char *p, int len) +static void print_hex(unsigned char *p, int len) { int i; for (i=0; iar_pln != 4) return 0; - if (ah->ar_hln != me.sll_halen) + if (ah->ar_hln != ((struct sockaddr_ll *)&me)->sll_halen) return 0; if (len < sizeof(*ah) + 2*(4 + ah->ar_hln)) return 0; @@ -242,7 +435,7 @@ int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM) return 0; if (src.s_addr != dst_ip.s_addr) return 0; - if (memcmp(p+ah->ar_hln+4, &me.sll_addr, ah->ar_hln)) + if (memcmp(p+ah->ar_hln+4, ((struct sockaddr_ll *)&me)->sll_addr, ah->ar_hln)) return 0; } else { /* DAD packet was: @@ -260,7 +453,7 @@ int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM) */ if (src_ip.s_addr != dst.s_addr) return 0; - if (memcmp(p, &me.sll_addr, me.sll_halen) == 0) + if (memcmp(p, ((struct sockaddr_ll *)&me)->sll_addr, ((struct sockaddr_ll *)&me)->sll_halen) == 0) return 0; if (src.s_addr && src.s_addr != dst_ip.s_addr) return 0; @@ -276,7 +469,7 @@ int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM) printf("for %s ", inet_ntoa(dst_ip)); s_printed = 1; } - if (memcmp(p+ah->ar_hln+4, me.sll_addr, ah->ar_hln)) { + if (memcmp(p+ah->ar_hln+4, ((struct sockaddr_ll *)&me)->sll_addr, ah->ar_hln)) { if (!s_printed) printf("for "); printf("["); @@ -299,16 +492,78 @@ int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM) brd_recv++; if (ah->ar_op == htons(ARPOP_REQUEST)) req_recv++; - if (quit_on_reply) + if (quit_on_reply || (count == 0 && received == sent)) finish(); if(!broadcast_only) { - memcpy(he.sll_addr, p, me.sll_halen); + memcpy(((struct sockaddr_ll *)&he)->sll_addr, p, ((struct sockaddr_ll *)&me)->sll_halen); unicasting=1; } return 1; } -#include +#ifdef USE_SYSFS +union sysfs_devattr_value { + unsigned long ulong; + void *ptr; +}; + +enum { + SYSFS_DEVATTR_IFINDEX, + SYSFS_DEVATTR_FLAGS, + SYSFS_DEVATTR_ADDR_LEN, +#if 0 + SYSFS_DEVATTR_TYPE, + SYSFS_DEVATTR_ADDRESS, +#endif + SYSFS_DEVATTR_BROADCAST, + SYSFS_DEVATTR_NUM +}; + +struct sysfs_devattr_values +{ + char *ifname; + union sysfs_devattr_value value[SYSFS_DEVATTR_NUM]; +}; + +static int sysfs_devattr_ulong_dec(char *ptr, struct sysfs_devattr_values *v, unsigned idx); +static int sysfs_devattr_ulong_hex(char *ptr, struct sysfs_devattr_values *v, unsigned idx); +static int sysfs_devattr_macaddr(char *ptr, struct sysfs_devattr_values *v, unsigned idx); + +struct sysfs_devattrs { + const char *name; + int (*handler)(char *ptr, struct sysfs_devattr_values *v, unsigned int idx); + int free; +} sysfs_devattrs[SYSFS_DEVATTR_NUM] = { + [SYSFS_DEVATTR_IFINDEX] = { + .name = "ifindex", + .handler = sysfs_devattr_ulong_dec, + }, + [SYSFS_DEVATTR_ADDR_LEN] = { + .name = "addr_len", + .handler = sysfs_devattr_ulong_dec, + }, + [SYSFS_DEVATTR_FLAGS] = { + .name = "flags", + .handler = sysfs_devattr_ulong_hex, + }, +#if 0 + [SYSFS_DEVATTR_TYPE] = { + .name = "type", + .handler = sysfs_devattr_ulong_dec, + }, + [SYSFS_DEVATTR_ADDRESS] = { + .name = "address", + .handler = sysfs_devattr_macaddr, + .free = 1, + }, +#endif + [SYSFS_DEVATTR_BROADCAST] = { + .name = "broadcast", + .handler = sysfs_devattr_macaddr, + .free = 1, + }, +}; +#endif static void byebye(int nsig) { @@ -317,26 +572,477 @@ static void byebye(int nsig) exit(nsig); } +/* + * find_device() + * + * This function checks 1) if the device (if given) is okay for ARP, + * or 2) find fist appropriate device on the system. + * + * Return value: + * >0 : Succeeded, and appropriate device not found. + * device.ifindex remains 0. + * 0 : Succeeded, and approptiate device found. + * device.ifindex is set. + * <0 : Failed. Support not found, or other + * : system error. Try other method. + * + * If an appropriate device found, it is recorded inside the + * "device" variable for later reference. + * + * We have several implementations for this. + * by_ifaddrs(): requires getifaddr() in glibc, and rtnetlink in + * kernel. default and recommended for recent systems. + * by_sysfs(): requires libsysfs , and sysfs in kernel. + * by_ioctl(): unable to list devices without ipv4 address; this + * means, you need to supply the device name for + * DAD purpose. + */ +/* Common check for ifa->ifa_flags */ +static int check_ifflags(unsigned int ifflags, int fatal) +{ + if (!(ifflags & IFF_UP)) { + if (fatal) { + if (!quiet) + printf("Interface \"%s\" is down\n", device.name); + exit(2); + } + return -1; + } + if (ifflags & (IFF_NOARP | IFF_LOOPBACK)) { + if (fatal) { + if (!quiet) + printf("Interface \"%s\" is not ARPable\n", device.name); + exit(dad ? 0 : 2); + } + return -1; + } + return 0; +} + +static int find_device_by_ifaddrs(void) +{ +#ifndef WITHOUT_IFADDRS + int rc; + struct ifaddrs *ifa0, *ifa; + int count = 0; + + rc = getifaddrs(&ifa0); + if (rc) { + perror("getifaddrs"); + return -1; + } + + for (ifa = ifa0; ifa; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + if (ifa->ifa_addr->sa_family != AF_PACKET) + continue; + if (device.name && ifa->ifa_name && strcmp(ifa->ifa_name, device.name)) + continue; + + if (check_ifflags(ifa->ifa_flags, device.name != NULL) < 0) + continue; + + if (!((struct sockaddr_ll *)ifa->ifa_addr)->sll_halen) + continue; + if (!ifa->ifa_broadaddr) + continue; + + device.ifa = ifa; + + if (count++) + break; + } + + if (count == 1 && device.ifa) { + device.ifindex = if_nametoindex(device.ifa->ifa_name); + if (!device.ifindex) { + perror("arping: if_nametoindex"); + freeifaddrs(ifa0); + return -1; + } + device.name = device.ifa->ifa_name; + return 0; + } + return 1; +#else + return -1; +#endif +} + +#ifdef USE_SYSFS +static void sysfs_devattr_values_init(struct sysfs_devattr_values *v, int do_free) +{ + int i; + if (do_free) { + free(v->ifname); + for (i = 0; i < SYSFS_DEVATTR_NUM; i++) { + if (sysfs_devattrs[i].free) + free(v->value[i].ptr); + } + } + memset(v, 0, sizeof(*v)); +} + +static int sysfs_devattr_ulong(char *ptr, struct sysfs_devattr_values *v, unsigned int idx, + unsigned int base) +{ + unsigned long *p; + char *ep; + + if (!ptr || !v) + return -1; + + p = &v->value[idx].ulong; + errno = 0; + *p = strtoul(ptr, &ep, base); + if ((*ptr && isspace(*ptr & 0xff)) || errno || (*ep != '\0' && *ep != '\n')) + goto out; + + return 0; +out: + return -1; +} + +static int sysfs_devattr_ulong_dec(char *ptr, struct sysfs_devattr_values *v, unsigned int idx) +{ + int rc = sysfs_devattr_ulong(ptr, v, idx, 10); + return rc; +} + +static int sysfs_devattr_ulong_hex(char *ptr, struct sysfs_devattr_values *v, unsigned int idx) +{ + int rc = sysfs_devattr_ulong(ptr, v, idx, 16); + return rc; +} + +static int sysfs_devattr_macaddr(char *ptr, struct sysfs_devattr_values *v, unsigned int idx) +{ + unsigned char *m; + int i; + unsigned int addrlen; + + if (!ptr || !v) + return -1; + + addrlen = v->value[SYSFS_DEVATTR_ADDR_LEN].ulong; + m = malloc(addrlen); + + for (i = 0; i < addrlen; i++) { + if (i && *(ptr + i * 3 - 1) != ':') + goto out; + if (sscanf(ptr + i * 3, "%02hhx", &m[i]) != 1) + goto out; + } + + v->value[idx].ptr = m; + return 0; +out: + free(m); + return -1; +} +#endif + +static int find_device_by_sysfs(void) +{ + int rc = -1; +#ifdef USE_SYSFS + struct sysfs_class *cls_net; + struct dlist *dev_list; + struct sysfs_class_device *dev; + struct sysfs_attribute *dev_attr; + struct sysfs_devattr_values sysfs_devattr_values; + int count = 0; + + if (!device.sysfs) { + device.sysfs = malloc(sizeof(*device.sysfs)); + sysfs_devattr_values_init(device.sysfs, 0); + } + + cls_net = sysfs_open_class("net"); + if (!cls_net) { + perror("sysfs_open_class"); + return -1; + } + + dev_list = sysfs_get_class_devices(cls_net); + if (!dev_list) { + perror("sysfs_get_class_devices"); + goto out; + } + + sysfs_devattr_values_init(&sysfs_devattr_values, 0); + + dlist_for_each_data(dev_list, dev, struct sysfs_class_device) { + int i; + int rc = -1; + + if (device.name && strcmp(dev->name, device.name)) + goto do_next; + + sysfs_devattr_values_init(&sysfs_devattr_values, 1); + + for (i = 0; i < SYSFS_DEVATTR_NUM; i++) { + + dev_attr = sysfs_get_classdev_attr(dev, sysfs_devattrs[i].name); + if (!dev_attr) { + perror("sysfs_get_classdev_attr"); + rc = -1; + break; + } + if (sysfs_read_attribute(dev_attr)) { + perror("sysfs_read_attribute"); + rc = -1; + break; + } + rc = sysfs_devattrs[i].handler(dev_attr->value, &sysfs_devattr_values, i); + + if (rc < 0) + break; + } + + if (rc < 0) + goto do_next; + + if (check_ifflags(sysfs_devattr_values.value[SYSFS_DEVATTR_FLAGS].ulong, + device.name != NULL) < 0) + goto do_next; + + if (!sysfs_devattr_values.value[SYSFS_DEVATTR_ADDR_LEN].ulong) + goto do_next; + + if (device.sysfs->value[SYSFS_DEVATTR_IFINDEX].ulong) { + if (device.sysfs->value[SYSFS_DEVATTR_FLAGS].ulong & IFF_RUNNING) + goto do_next; + } + + sysfs_devattr_values.ifname = strdup(dev->name); + if (!sysfs_devattr_values.ifname) { + perror("malloc"); + goto out; + } + + sysfs_devattr_values_init(device.sysfs, 1); + memcpy(device.sysfs, &sysfs_devattr_values, sizeof(*device.sysfs)); + sysfs_devattr_values_init(&sysfs_devattr_values, 0); + + if (count++) + break; + + continue; +do_next: + sysfs_devattr_values_init(&sysfs_devattr_values, 1); + } + + if (count == 1) { + device.ifindex = device.sysfs->value[SYSFS_DEVATTR_IFINDEX].ulong; + device.name = device.sysfs->ifname; + } + rc = !device.ifindex; +out: + sysfs_close_class(cls_net); +#endif + return rc; +} + +static int check_device_by_ioctl(int s, struct ifreq *ifr) +{ + if (ioctl(s, SIOCGIFFLAGS, ifr) < 0) { + perror("ioctl(SIOCGIFINDEX"); + return -1; + } + + if (check_ifflags(ifr->ifr_flags, device.name != NULL) < 0) + return 1; + + if (ioctl(s, SIOCGIFINDEX, ifr) < 0) { + perror("ioctl(SIOCGIFINDEX"); + return -1; + } + + return 0; +} + +static int find_device_by_ioctl(void) +{ + int s; + struct ifreq *ifr0, *ifr, *ifr_end; + size_t ifrsize = sizeof(*ifr); + struct ifconf ifc; + static struct ifreq ifrbuf; + int count = 0; + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket"); + return -1; + } + + memset(&ifrbuf, 0, sizeof(ifrbuf)); + + if (device.name) { + strncpy(ifrbuf.ifr_name, device.name, sizeof(ifrbuf.ifr_name) - 1); + if (check_device_by_ioctl(s, &ifrbuf)) + goto out; + count++; + } else { + do { + int rc; + ifr0 = malloc(ifrsize); + if (!ifr0) { + perror("malloc"); + goto out; + } + + ifc.ifc_buf = (char *)ifr0; + ifc.ifc_len = ifrsize; + + rc = ioctl(s, SIOCGIFCONF, &ifc); + if (rc < 0) { + perror("ioctl(SIOCFIFCONF"); + goto out; + } + + if (ifc.ifc_len + sizeof(*ifr0) + sizeof(struct sockaddr_storage) - sizeof(struct sockaddr) <= ifrsize) + break; + ifrsize *= 2; + free(ifr0); + ifr0 = NULL; + } while(ifrsize < INT_MAX / 2); + + if (!ifr0) { + fprintf(stderr, "arping: too many interfaces!?\n"); + goto out; + } + + ifr_end = (struct ifreq *)(((char *)ifr0) + ifc.ifc_len - sizeof(*ifr0)); + for (ifr = ifr0; ifr <= ifr_end; ifr++) { + if (check_device_by_ioctl(s, &ifrbuf)) + continue; + memcpy(&ifrbuf.ifr_name, ifr->ifr_name, sizeof(ifrbuf.ifr_name)); + if (count++) + break; + } + } + + close(s); + + if (count == 1) { + device.ifindex = ifrbuf.ifr_ifindex; + device.name = ifrbuf.ifr_name; + } + return !device.ifindex; +out: + close(s); + return -1; +} + +static int find_device(void) +{ + int rc; + rc = find_device_by_ifaddrs(); + if (rc >= 0) + goto out; + rc = find_device_by_sysfs(); + if (rc >= 0) + goto out; + rc = find_device_by_ioctl(); +out: + return rc; +} + +/* + * set_device_broadcast() + * + * This fills the device "broadcast address" + * based on information found by find_device() funcion. + */ +static int set_device_broadcast_ifaddrs_one(struct device *device, unsigned char *ba, size_t balen, int fatal) +{ +#ifndef WITHOUT_IFADDRS + struct ifaddrs *ifa; + struct sockaddr_ll *sll; + + if (!device) + return -1; + + ifa = device->ifa; + if (!ifa) + return -1; + + sll = (struct sockaddr_ll *)ifa->ifa_broadaddr; + + if (sll->sll_halen != balen) { + if (fatal) { + if (!quiet) + printf("Address length does not match...\n"); + exit(2); + } + return -1; + } + memcpy(ba, sll->sll_addr, sll->sll_halen); + return 0; +#else + return -1; +#endif +} +static int set_device_broadcast_sysfs(struct device *device, unsigned char *ba, size_t balen) +{ +#ifdef USE_SYSFS + struct sysfs_devattr_values *v; + if (!device) + return -1; + v = device->sysfs; + if (!v) + return -1; + if (v->value[SYSFS_DEVATTR_ADDR_LEN].ulong != balen) + return -1; + memcpy(ba, v->value[SYSFS_DEVATTR_BROADCAST].ptr, balen); + return 0; +#else + return -1; +#endif +} + +static int set_device_broadcast_fallback(struct device *device, unsigned char *ba, size_t balen) +{ + if (!quiet) + fprintf(stderr, "WARNING: using default broadcast address.\n"); + memset(ba, -1, balen); + return 0; +} + +static void set_device_broadcast(struct device *dev, unsigned char *ba, size_t balen) +{ + if (!set_device_broadcast_ifaddrs_one(dev, ba, balen, 0)) + return; + if (!set_device_broadcast_sysfs(dev, ba, balen)) + return; + set_device_broadcast_fallback(dev, ba, balen); +} + int main(int argc, char **argv) { int socket_errno; int ch; - uid_t uid = getuid(); int hb_mode = 0; signal(SIGTERM, byebye); signal(SIGPIPE, byebye); - - device = strdup("eth0"); - + + limit_capabilities(); + +#ifdef USE_IDN + setlocale(LC_ALL, ""); +#endif + + enable_capability_raw(); + s = socket(PF_PACKET, SOCK_DGRAM, 0); socket_errno = errno; - if (setuid(uid)) { - perror("arping: setuid"); - exit(-1); - } + disable_capability_raw(); while ((ch = getopt(argc, argv, "h?bfDUAqc:w:s:I:Vr:i:p:")) != EOF) { switch(ch) { @@ -367,7 +1073,7 @@ main(int argc, char **argv) timeout = atoi(optarg); break; case 'I': - device = optarg; + device.name = optarg; break; case 'f': quit_on_reply=1; @@ -376,7 +1082,7 @@ main(int argc, char **argv) source = optarg; break; case 'V': - printf("send_arp utility\n"); + printf("send_arp utility, based on arping from iputils-%s\n", SNAPSHOT); exit(0); case 'p': case 'i': @@ -405,7 +1111,7 @@ main(int argc, char **argv) */ unsolicited = 1; - device = argv[optind]; + device.name = argv[optind]; target = argv[optind+1]; } else { @@ -417,10 +1123,8 @@ main(int argc, char **argv) target = *argv; } - if (device == NULL) { - fprintf(stderr, "arping: device (option -I) is required\n"); - usage(); - } + if (device.name && !*device.name) + device.name = NULL; if (s < 0) { errno = socket_errno; @@ -428,39 +1132,42 @@ main(int argc, char **argv) exit(2); } - if (1) { - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, device, IFNAMSIZ-1); - if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { - fprintf(stderr, "arping: unknown iface %s\n", device); - exit(2); - } - ifindex = ifr.ifr_ifindex; + if (find_device() < 0) + exit(2); - if (ioctl(s, SIOCGIFFLAGS, (char*)&ifr)) { - perror("ioctl(SIOCGIFFLAGS)"); + if (!device.ifindex) { + if (device.name) { + fprintf(stderr, "arping: Device %s not available.\n", device.name); exit(2); } - if (!(ifr.ifr_flags&IFF_UP)) { - if (!quiet) - printf("Interface \"%s\" is down\n", device); - exit(2); - } - if (ifr.ifr_flags&(IFF_NOARP|IFF_LOOPBACK)) { - if (!quiet) - printf("Interface \"%s\" is not ARPable\n", device); - exit(dad?0:2); - } + fprintf(stderr, "arping: device (option -I) is required.\n"); + usage(); } if (inet_aton(target, &dst) != 1) { struct hostent *hp; - hp = gethostbyname2(target, AF_INET); + char *idn = target; +#ifdef USE_IDN + int rc; + + rc = idna_to_ascii_lz(target, &idn, 0); + + if (rc != IDNA_SUCCESS) { + fprintf(stderr, "arping: IDN encoding failed: %s\n", idna_strerror(rc)); + exit(2); + } +#endif + + hp = gethostbyname2(idn, AF_INET); if (!hp) { fprintf(stderr, "arping: unknown host %s\n", target); exit(2); } + +#ifdef USE_IDN + free(idn); +#endif + memcpy(&dst, hp->h_addr, 4); } @@ -480,9 +1187,13 @@ main(int argc, char **argv) perror("socket"); exit(2); } - if (device) { - if (setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device)+1) == -1) + if (device.name) { + enable_capability_raw(); + + if (setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device.name, strlen(device.name)+1) == -1) perror("WARNING: interface is ignored"); + + disable_capability_raw(); } memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; @@ -514,9 +1225,9 @@ main(int argc, char **argv) close(probe_fd); }; - me.sll_family = AF_PACKET; - me.sll_ifindex = ifindex; - me.sll_protocol = htons(ETH_P_ARP); + ((struct sockaddr_ll *)&me)->sll_family = AF_PACKET; + ((struct sockaddr_ll *)&me)->sll_ifindex = device.ifindex; + ((struct sockaddr_ll *)&me)->sll_protocol = htons(ETH_P_ARP); if (bind(s, (struct sockaddr*)&me, sizeof(me)) == -1) { perror("bind"); exit(2); @@ -529,18 +1240,20 @@ main(int argc, char **argv) exit(2); } } - if (me.sll_halen == 0) { + if (((struct sockaddr_ll *)&me)->sll_halen == 0) { if (!quiet) - printf("Interface \"%s\" is not ARPable (no ll address)\n", device); + printf("Interface \"%s\" is not ARPable (no ll address)\n", device.name); exit(dad?0:2); } he = me; - memset(he.sll_addr, -1, he.sll_halen); + + set_device_broadcast(&device, ((struct sockaddr_ll *)&he)->sll_addr, + ((struct sockaddr_ll *)&he)->sll_halen); if (!quiet) { printf("ARPING %s ", inet_ntoa(dst)); - printf("from %s %s\n", inet_ntoa(src), device ? : ""); + printf("from %s %s\n", inet_ntoa(src), device.name ? : ""); } if (!src.s_addr && !dad) { @@ -548,6 +1261,8 @@ main(int argc, char **argv) exit(2); } + drop_capabilities(); + set_signal(SIGINT, finish); set_signal(SIGALRM, catcher); @@ -556,7 +1271,7 @@ main(int argc, char **argv) while(1) { sigset_t sset, osset; unsigned char packet[4096]; - struct sockaddr_ll from; + struct sockaddr_storage from; socklen_t alen = sizeof(from); int cc; @@ -565,11 +1280,12 @@ main(int argc, char **argv) perror("arping: recvfrom"); continue; } + sigemptyset(&sset); sigaddset(&sset, SIGALRM); sigaddset(&sset, SIGINT); sigprocmask(SIG_BLOCK, &sset, &osset); - recv_pack(packet, cc, &from); + recv_pack(packet, cc, (struct sockaddr_ll *)&from); sigprocmask(SIG_SETMASK, &osset, NULL); } }