busybox/networking/arp.c
James Byrne 6937487be7 libbb: reduce the overhead of single parameter bb_error_msg() calls
Back in 2007, commit 0c97c9d437 ("'simple' error message functions by
Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower
overhead call to bb_perror_msg() when only a string was being printed
with no parameters. This saves space for some CPU architectures because
it avoids the overhead of a call to a variadic function. However there
has never been a simple version of bb_error_msg(), and since 2007 many
new calls to bb_perror_msg() have been added that only take a single
parameter and so could have been using bb_simple_perror_message().

This changeset introduces 'simple' versions of bb_info_msg(),
bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and
bb_herror_msg_and_die(), and replaces all calls that only take a
single parameter, or use something like ("%s", arg), with calls to the
corresponding 'simple' version.

Since it is likely that single parameter calls to the variadic functions
may be accidentally reintroduced in the future a new debugging config
option WARN_SIMPLE_MSG has been introduced. This uses some macro magic
which will cause any such calls to generate a warning, but this is
turned off by default to avoid use of the unpleasant macros in normal
circumstances.

This is a large changeset due to the number of calls that have been
replaced. The only files that contain changes other than simple
substitution of function calls are libbb.h, libbb/herror_msg.c,
libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c,
networking/udhcp/common.h and util-linux/mdev.c additonal macros have
been added for logging so that single parameter and multiple parameter
logging variants exist.

The amount of space saved varies considerably by architecture, and was
found to be as follows (for 'defconfig' using GCC 7.4):

Arm:     -92 bytes
MIPS:    -52 bytes
PPC:   -1836 bytes
x86_64: -938 bytes

Note that for the MIPS architecture only an exception had to be made
disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h)
because it made these files larger on MIPS.

Signed-off-by: James Byrne <james.byrne@origamienergy.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2019-07-02 11:35:03 +02:00

542 lines
12 KiB
C

/* vi: set sw=4 ts=4: */
/*
* arp.c - Manipulate the system ARP cache
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Author: Fred N. van Kempen, <waltje at uwalt.nl.mugnet.org>
* Busybox port: Paul van Gool <pvangool at mimotech.com>
*
* modified for getopt32 by Arne Bernin <arne [at] alamut.de>
*/
//config:config ARP
//config: bool "arp (10 kb)"
//config: default y
//config: select PLATFORM_LINUX
//config: help
//config: Manipulate the system ARP cache.
//applet:IF_ARP(APPLET(arp, BB_DIR_SBIN, BB_SUID_DROP))
//kbuild:lib-$(CONFIG_ARP) += arp.o interface.o
//usage:#define arp_trivial_usage
//usage: "\n[-vn] [-H HWTYPE] [-i IF] -a [HOSTNAME]"
//usage: "\n[-v] [-i IF] -d HOSTNAME [pub]"
//usage: "\n[-v] [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [temp]"
//usage: "\n[-v] [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [netmask MASK] pub"
//usage: "\n[-v] [-H HWTYPE] [-i IF] -Ds HOSTNAME IFACE [netmask MASK] pub"
//usage:#define arp_full_usage "\n\n"
//usage: "Manipulate ARP cache\n"
//usage: "\n -a Display (all) hosts"
//usage: "\n -d Delete ARP entry"
//usage: "\n -s Set new entry"
//usage: "\n -v Verbose"
//usage: "\n -n Don't resolve names"
//usage: "\n -i IF Network interface"
//usage: "\n -D Read HWADDR from IFACE"
//usage: "\n -A,-p AF Protocol family"
//usage: "\n -H HWTYPE Hardware address type"
#include "libbb.h"
#include "common_bufsiz.h"
#include "inet_common.h"
#include <arpa/inet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/ether.h>
#include <netpacket/packet.h>
#define DEBUG 0
#define DFLT_AF "inet"
#define DFLT_HW "ether"
enum {
ARP_OPT_A = (1 << 0),
ARP_OPT_p = (1 << 1),
ARP_OPT_H = (1 << 2),
ARP_OPT_t = (1 << 3),
ARP_OPT_i = (1 << 4),
ARP_OPT_a = (1 << 5),
ARP_OPT_d = (1 << 6),
ARP_OPT_n = (1 << 7), /* do not resolve addresses */
ARP_OPT_D = (1 << 8), /* HW-address is devicename */
ARP_OPT_s = (1 << 9),
ARP_OPT_v = (1 << 10) * DEBUG, /* debugging output flag */
};
enum {
sockfd = 3, /* active socket descriptor */
};
struct globals {
const struct aftype *ap; /* current address family */
const struct hwtype *hw; /* current hardware type */
const char *device; /* current device */
smallint hw_set; /* flag if hw-type was set (-H) */
} FIX_ALIASING;
#define G (*(struct globals*)bb_common_bufsiz1)
#define ap (G.ap )
#define hw (G.hw )
#define device (G.device )
#define hw_set (G.hw_set )
#define INIT_G() do { \
setup_common_bufsiz(); \
device = ""; \
} while (0)
static const char options[] ALIGN1 =
"pub\0"
"priv\0"
"temp\0"
"trail\0"
"dontpub\0"
"auto\0"
"dev\0"
"netmask\0";
/* Delete an entry from the ARP cache. */
/* Called only from main, once */
static int arp_del(char **args)
{
char *host;
struct arpreq req;
struct sockaddr sa;
int flags = 0;
int err;
memset(&req, 0, sizeof(req));
/* Resolve the host name. */
host = *args;
if (ap->input(host, &sa) < 0) {
bb_simple_herror_msg_and_die(host);
}
/* If a host has more than one address, use the correct one! */
memcpy(&req.arp_pa, &sa, sizeof(struct sockaddr));
if (hw_set)
req.arp_ha.sa_family = hw->type;
req.arp_flags = ATF_PERM;
args++;
while (*args != NULL) {
switch (index_in_strings(options, *args)) {
case 0: /* "pub" */
flags |= 1;
args++;
break;
case 1: /* "priv" */
flags |= 2;
args++;
break;
case 2: /* "temp" */
req.arp_flags &= ~ATF_PERM;
args++;
break;
case 3: /* "trail" */
req.arp_flags |= ATF_USETRAILERS;
args++;
break;
case 4: /* "dontpub" */
#ifdef HAVE_ATF_DONTPUB
req.arp_flags |= ATF_DONTPUB;
#else
bb_simple_error_msg("feature ATF_DONTPUB is not supported");
#endif
args++;
break;
case 5: /* "auto" */
#ifdef HAVE_ATF_MAGIC
req.arp_flags |= ATF_MAGIC;
#else
bb_simple_error_msg("feature ATF_MAGIC is not supported");
#endif
args++;
break;
case 6: /* "dev" */
if (*++args == NULL)
bb_show_usage();
device = *args;
args++;
break;
case 7: /* "netmask" */
if (*++args == NULL)
bb_show_usage();
if (strcmp(*args, "255.255.255.255") != 0) {
host = *args;
if (ap->input(host, &sa) < 0) {
bb_simple_herror_msg_and_die(host);
}
memcpy(&req.arp_netmask, &sa, sizeof(struct sockaddr));
req.arp_flags |= ATF_NETMASK;
}
args++;
break;
default:
bb_show_usage();
break;
}
}
if (flags == 0)
flags = 3;
strncpy_IFNAMSIZ(req.arp_dev, device);
err = -1;
/* Call the kernel. */
if (flags & 2) {
if (option_mask32 & ARP_OPT_v)
bb_simple_error_msg("SIOCDARP(nopub)");
err = ioctl(sockfd, SIOCDARP, &req);
if (err < 0) {
if (errno == ENXIO) {
if (flags & 1)
goto nopub;
printf("No ARP entry for %s\n", host);
return -1;
}
bb_simple_perror_msg_and_die("SIOCDARP(priv)");
}
}
if ((flags & 1) && err) {
nopub:
req.arp_flags |= ATF_PUBL;
if (option_mask32 & ARP_OPT_v)
bb_simple_error_msg("SIOCDARP(pub)");
if (ioctl(sockfd, SIOCDARP, &req) < 0) {
if (errno == ENXIO) {
printf("No ARP entry for %s\n", host);
return -1;
}
bb_simple_perror_msg_and_die("SIOCDARP(pub)");
}
}
return 0;
}
/* Get the hardware address to a specified interface name */
static void arp_getdevhw(char *ifname, struct sockaddr *sa)
{
struct ifreq ifr;
const struct hwtype *xhw;
strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
ioctl_or_perror_and_die(sockfd, SIOCGIFHWADDR, &ifr,
"can't get HW-Address for '%s'", ifname);
if (hw_set && (ifr.ifr_hwaddr.sa_family != hw->type)) {
bb_simple_error_msg_and_die("protocol type mismatch");
}
memcpy(sa, &(ifr.ifr_hwaddr), sizeof(struct sockaddr));
if (option_mask32 & ARP_OPT_v) {
xhw = get_hwntype(ifr.ifr_hwaddr.sa_family);
if (!xhw || !xhw->print) {
xhw = get_hwntype(-1);
}
bb_error_msg("device '%s' has HW address %s '%s'",
ifname, xhw->name,
xhw->print((unsigned char *) &ifr.ifr_hwaddr.sa_data));
}
}
/* Set an entry in the ARP cache. */
/* Called only from main, once */
static int arp_set(char **args)
{
char *host;
struct arpreq req;
struct sockaddr sa;
int flags;
memset(&req, 0, sizeof(req));
host = *args++;
if (ap->input(host, &sa) < 0) {
bb_simple_herror_msg_and_die(host);
}
/* If a host has more than one address, use the correct one! */
memcpy(&req.arp_pa, &sa, sizeof(struct sockaddr));
/* Fetch the hardware address. */
if (*args == NULL) {
bb_simple_error_msg_and_die("need hardware address");
}
if (option_mask32 & ARP_OPT_D) {
arp_getdevhw(*args++, &req.arp_ha);
} else {
if (hw->input(*args++, &req.arp_ha) < 0) {
bb_simple_error_msg_and_die("invalid hardware address");
}
}
/* Check out any modifiers. */
flags = ATF_PERM | ATF_COM;
while (*args != NULL) {
switch (index_in_strings(options, *args)) {
case 0: /* "pub" */
flags |= ATF_PUBL;
args++;
break;
case 1: /* "priv" */
flags &= ~ATF_PUBL;
args++;
break;
case 2: /* "temp" */
flags &= ~ATF_PERM;
args++;
break;
case 3: /* "trail" */
flags |= ATF_USETRAILERS;
args++;
break;
case 4: /* "dontpub" */
#ifdef HAVE_ATF_DONTPUB
flags |= ATF_DONTPUB;
#else
bb_simple_error_msg("feature ATF_DONTPUB is not supported");
#endif
args++;
break;
case 5: /* "auto" */
#ifdef HAVE_ATF_MAGIC
flags |= ATF_MAGIC;
#else
bb_simple_error_msg("feature ATF_MAGIC is not supported");
#endif
args++;
break;
case 6: /* "dev" */
if (*++args == NULL)
bb_show_usage();
device = *args;
args++;
break;
case 7: /* "netmask" */
if (*++args == NULL)
bb_show_usage();
if (strcmp(*args, "255.255.255.255") != 0) {
host = *args;
if (ap->input(host, &sa) < 0) {
bb_simple_herror_msg_and_die(host);
}
memcpy(&req.arp_netmask, &sa, sizeof(struct sockaddr));
flags |= ATF_NETMASK;
}
args++;
break;
default:
bb_show_usage();
break;
}
}
/* Fill in the remainder of the request. */
req.arp_flags = flags;
strncpy_IFNAMSIZ(req.arp_dev, device);
/* Call the kernel. */
if (option_mask32 & ARP_OPT_v)
bb_simple_error_msg("SIOCSARP()");
xioctl(sockfd, SIOCSARP, &req);
return 0;
}
/* Print the contents of an ARP request block. */
static void
arp_disp(const char *name, char *ip, int type, int arp_flags,
char *hwa, char *mask, char *dev)
{
static const int arp_masks[] = {
ATF_PERM, ATF_PUBL,
#ifdef HAVE_ATF_MAGIC
ATF_MAGIC,
#endif
#ifdef HAVE_ATF_DONTPUB
ATF_DONTPUB,
#endif
ATF_USETRAILERS,
};
static const char arp_labels[] ALIGN1 = "PERM\0""PUP\0"
#ifdef HAVE_ATF_MAGIC
"AUTO\0"
#endif
#ifdef HAVE_ATF_DONTPUB
"DONTPUB\0"
#endif
"TRAIL\0"
;
const struct hwtype *xhw;
xhw = get_hwntype(type);
if (xhw == NULL)
xhw = get_hwtype(DFLT_HW);
printf("%s (%s) at ", name, ip);
if (!(arp_flags & ATF_COM)) {
if (arp_flags & ATF_PUBL)
printf("* ");
else
printf("<incomplete> ");
} else {
printf("%s [%s] ", hwa, xhw->name);
}
if (arp_flags & ATF_NETMASK)
printf("netmask %s ", mask);
print_flags_separated(arp_masks, arp_labels, arp_flags, " ");
printf(" on %s\n", dev);
}
/* Display the contents of the ARP cache in the kernel. */
/* Called only from main, once */
static int arp_show(char *name)
{
const char *host;
const char *hostname;
FILE *fp;
struct sockaddr sa;
int type, flags;
int num;
unsigned entries = 0, shown = 0;
char ip[128];
char hwa[128];
char mask[128];
char line[128];
char dev[128];
host = NULL;
if (name != NULL) {
/* Resolve the host name. */
if (ap->input(name, &sa) < 0) {
bb_simple_herror_msg_and_die(name);
}
host = xstrdup(ap->sprint(&sa, 1));
}
fp = xfopen_for_read("/proc/net/arp");
/* Bypass header -- read one line */
fgets(line, sizeof(line), fp);
/* Read the ARP cache entries. */
while (fgets(line, sizeof(line), fp)) {
mask[0] = '-'; mask[1] = '\0';
dev[0] = '-'; dev[1] = '\0';
/* All these strings can't overflow
* because fgets above reads limited amount of data */
num = sscanf(line, "%s 0x%x 0x%x %s %s %s\n",
ip, &type, &flags, hwa, mask, dev);
if (num < 4)
break;
entries++;
/* if the user specified hw-type differs, skip it */
if (hw_set && (type != hw->type))
continue;
/* if the user specified address differs, skip it */
if (host && strcmp(ip, host) != 0)
continue;
/* if the user specified device differs, skip it */
if (device[0] && strcmp(dev, device) != 0)
continue;
shown++;
/* This IS ugly but it works -be */
hostname = "?";
if (!(option_mask32 & ARP_OPT_n)) {
if (ap->input(ip, &sa) < 0)
hostname = ip;
else
hostname = ap->sprint(&sa, (option_mask32 & ARP_OPT_n) | 0x8000);
if (strcmp(hostname, ip) == 0)
hostname = "?";
}
arp_disp(hostname, ip, type, flags, hwa, mask, dev);
}
if (option_mask32 & ARP_OPT_v)
printf("Entries: %u\tSkipped: %u\tFound: %u\n",
entries, entries - shown, shown);
if (!shown) {
if (hw_set || host || device[0])
printf("No match found in %u entries\n", entries);
}
if (ENABLE_FEATURE_CLEAN_UP) {
free((char*)host);
fclose(fp);
}
return 0;
}
int arp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int arp_main(int argc UNUSED_PARAM, char **argv)
{
const char *hw_type;
const char *protocol;
unsigned opts;
INIT_G();
xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), sockfd);
ap = get_aftype(DFLT_AF);
/* Defaults are always supported */
//if (!ap)
// bb_error_msg_and_die("%s: %s not supported", DFLT_AF, "address family");
hw = get_hwtype(DFLT_HW);
//if (!hw)
// bb_error_msg_and_die("%s: %s not supported", DFLT_HW, "hardware type");
opts = getopt32(argv, "A:p:H:t:i:adnDsv", &protocol, &protocol,
&hw_type, &hw_type, &device);
argv += optind;
if (opts & (ARP_OPT_A | ARP_OPT_p)) {
ap = get_aftype(protocol);
if (!ap)
bb_error_msg_and_die("%s: unknown %s", protocol, "address family");
}
if (opts & (ARP_OPT_H | ARP_OPT_t)) {
hw = get_hwtype(hw_type);
if (!hw)
bb_error_msg_and_die("%s: unknown %s", hw_type, "hardware type");
hw_set = 1;
}
//if (opts & ARP_OPT_i)... -i
if (ap->af != AF_INET) {
bb_error_msg_and_die("%s: kernel only supports 'inet'", ap->name);
}
if (hw->alen <= 0) {
bb_error_msg_and_die("%s: %s without ARP support",
hw->name, "hardware type");
}
/* Now see what we have to do here... */
if (opts & (ARP_OPT_d | ARP_OPT_s)) {
if (argv[0] == NULL)
bb_simple_error_msg_and_die("need host name");
if (opts & ARP_OPT_s)
return arp_set(argv);
return arp_del(argv);
}
//if (opts & ARP_OPT_a) - default
return arp_show(argv[0]);
}