busybox/networking/libiproute/ipaddress.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

777 lines
20 KiB
C

/* vi: set sw=4 ts=4: */
/*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
*
* Changes:
* Laszlo Valko <valko@linux.karinthy.hu> 990223: address label must be zero terminated
*/
#include <fnmatch.h>
#include <net/if.h>
#include <net/if_arp.h>
#include "ip_common.h" /* #include "libbb.h" is inside */
#include "common_bufsiz.h"
#include "rt_names.h"
#include "utils.h"
#ifndef IFF_LOWER_UP
/* from linux/if.h */
#define IFF_LOWER_UP 0x10000 /* driver signals L1 up */
#endif
struct filter_t {
char *label;
/* Flush cmd buf. If !NULL, print_addrinfo() constructs flush commands in it */
char *flushb;
struct rtnl_handle *rth;
int scope, scopemask;
int flags, flagmask;
int flushp;
int flushe;
int ifindex;
family_t family;
smallint showqueue;
smallint oneline;
smallint up;
/* Misnomer. Does not mean "flushed something" */
/* More like "flush commands were constructed by print_addrinfo()" */
smallint flushed;
inet_prefix pfx;
} FIX_ALIASING;
typedef struct filter_t filter_t;
#define G_filter (*(filter_t*)bb_common_bufsiz1)
#define INIT_G() do { setup_common_bufsiz(); } while (0)
static void print_link_flags(unsigned flags, unsigned mdown)
{
static const int flag_masks[] = {
IFF_LOOPBACK, IFF_BROADCAST, IFF_POINTOPOINT,
IFF_MULTICAST, IFF_NOARP, IFF_UP, IFF_LOWER_UP };
static const char flag_labels[] ALIGN1 =
"LOOPBACK\0""BROADCAST\0""POINTOPOINT\0"
"MULTICAST\0""NOARP\0""UP\0""LOWER_UP\0";
bb_putchar('<');
if (flags & IFF_UP && !(flags & IFF_RUNNING))
printf("NO-CARRIER,");
flags &= ~IFF_RUNNING;
#if 0
_PF(ALLMULTI);
_PF(PROMISC);
_PF(MASTER);
_PF(SLAVE);
_PF(DEBUG);
_PF(DYNAMIC);
_PF(AUTOMEDIA);
_PF(PORTSEL);
_PF(NOTRAILERS);
#endif
flags = print_flags_separated(flag_masks, flag_labels, flags, ",");
if (flags)
printf("%x", flags);
if (mdown)
printf(",M-DOWN");
printf("> ");
}
static void print_queuelen(char *name)
{
struct ifreq ifr;
int s;
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0)
return;
memset(&ifr, 0, sizeof(ifr));
strncpy_IFNAMSIZ(ifr.ifr_name, name);
if (ioctl_or_warn(s, SIOCGIFTXQLEN, &ifr) < 0) {
close(s);
return;
}
close(s);
if (ifr.ifr_qlen)
printf("qlen %d", ifr.ifr_qlen);
}
static NOINLINE int print_linkinfo(const struct nlmsghdr *n)
{
struct ifinfomsg *ifi = NLMSG_DATA(n);
struct rtattr *tb[IFLA_MAX+1];
int len = n->nlmsg_len;
if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
return 0;
len -= NLMSG_LENGTH(sizeof(*ifi));
if (len < 0)
return -1;
if (G_filter.ifindex && ifi->ifi_index != G_filter.ifindex)
return 0;
if (G_filter.up && !(ifi->ifi_flags & IFF_UP))
return 0;
//memset(tb, 0, sizeof(tb)); - parse_rtattr does this
parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
if (tb[IFLA_IFNAME] == NULL) {
bb_simple_error_msg("nil ifname");
return -1;
}
if (G_filter.label
&& (!G_filter.family || G_filter.family == AF_PACKET)
&& fnmatch(G_filter.label, RTA_DATA(tb[IFLA_IFNAME]), 0)
) {
return 0;
}
if (n->nlmsg_type == RTM_DELLINK)
printf("Deleted ");
printf("%d: %s", ifi->ifi_index,
/*tb[IFLA_IFNAME] ? (char*)RTA_DATA(tb[IFLA_IFNAME]) : "<nil>" - we checked tb[IFLA_IFNAME] above*/
(char*)RTA_DATA(tb[IFLA_IFNAME])
);
{
unsigned m_flag = 0;
if (tb[IFLA_LINK]) {
int iflink = *(int*)RTA_DATA(tb[IFLA_LINK]);
if (iflink == 0)
printf("@NONE: ");
else {
printf("@%s: ", ll_index_to_name(iflink));
m_flag = ll_index_to_flags(iflink);
m_flag = !(m_flag & IFF_UP);
}
} else {
printf(": ");
}
print_link_flags(ifi->ifi_flags, m_flag);
}
if (tb[IFLA_MTU])
printf("mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU]));
if (tb[IFLA_QDISC])
printf("qdisc %s ", (char*)RTA_DATA(tb[IFLA_QDISC]));
#ifdef IFLA_MASTER
if (tb[IFLA_MASTER]) {
printf("master %s ", ll_index_to_name(*(int*)RTA_DATA(tb[IFLA_MASTER])));
}
#endif
/* IFLA_OPERSTATE was added to kernel with the same commit as IFF_DORMANT */
#ifdef IFF_DORMANT
if (tb[IFLA_OPERSTATE]) {
static const char operstate_labels[] ALIGN1 =
"UNKNOWN\0""NOTPRESENT\0""DOWN\0""LOWERLAYERDOWN\0"
"TESTING\0""DORMANT\0""UP\0";
printf("state %s ", nth_string(operstate_labels,
*(uint8_t *)RTA_DATA(tb[IFLA_OPERSTATE])));
}
#endif
if (G_filter.showqueue)
print_queuelen((char*)RTA_DATA(tb[IFLA_IFNAME]));
if (!G_filter.family || G_filter.family == AF_PACKET) {
SPRINT_BUF(b1);
printf("%c link/%s ", _SL_, ll_type_n2a(ifi->ifi_type, b1));
if (tb[IFLA_ADDRESS]) {
fputs(ll_addr_n2a(RTA_DATA(tb[IFLA_ADDRESS]),
RTA_PAYLOAD(tb[IFLA_ADDRESS]),
ifi->ifi_type,
b1, sizeof(b1)), stdout);
}
if (tb[IFLA_BROADCAST]) {
if (ifi->ifi_flags & IFF_POINTOPOINT)
printf(" peer ");
else
printf(" brd ");
fputs(ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]),
RTA_PAYLOAD(tb[IFLA_BROADCAST]),
ifi->ifi_type,
b1, sizeof(b1)), stdout);
}
}
bb_putchar('\n');
/*fflush_all();*/
return 0;
}
static int flush_update(void)
{
if (rtnl_send_check(G_filter.rth, G_filter.flushb, G_filter.flushp) < 0) {
bb_simple_perror_msg("can't send flush request");
return -1;
}
G_filter.flushp = 0;
return 0;
}
static int FAST_FUNC print_addrinfo(const struct sockaddr_nl *who UNUSED_PARAM,
struct nlmsghdr *n, void *arg UNUSED_PARAM)
{
struct ifaddrmsg *ifa = NLMSG_DATA(n);
int len = n->nlmsg_len;
struct rtattr *rta_tb[IFA_MAX+1];
if (n->nlmsg_type != RTM_NEWADDR && n->nlmsg_type != RTM_DELADDR)
return 0;
len -= NLMSG_LENGTH(sizeof(*ifa));
if (len < 0) {
bb_error_msg("wrong nlmsg len %d", len);
return -1;
}
if (G_filter.flushb && n->nlmsg_type != RTM_NEWADDR)
return 0;
//memset(rta_tb, 0, sizeof(rta_tb)); - parse_rtattr does this
parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
if (!rta_tb[IFA_LOCAL])
rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
if (!rta_tb[IFA_ADDRESS])
rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
if (G_filter.ifindex && G_filter.ifindex != ifa->ifa_index)
return 0;
if ((G_filter.scope ^ ifa->ifa_scope) & G_filter.scopemask)
return 0;
if ((G_filter.flags ^ ifa->ifa_flags) & G_filter.flagmask)
return 0;
if (G_filter.label) {
const char *label;
if (rta_tb[IFA_LABEL])
label = RTA_DATA(rta_tb[IFA_LABEL]);
else
label = ll_index_to_name(ifa->ifa_index);
if (fnmatch(G_filter.label, label, 0) != 0)
return 0;
}
if (G_filter.pfx.family) {
if (rta_tb[IFA_LOCAL]) {
inet_prefix dst;
memset(&dst, 0, sizeof(dst));
dst.family = ifa->ifa_family;
memcpy(&dst.data, RTA_DATA(rta_tb[IFA_LOCAL]), RTA_PAYLOAD(rta_tb[IFA_LOCAL]));
if (inet_addr_match(&dst, &G_filter.pfx, G_filter.pfx.bitlen))
return 0;
}
}
if (G_filter.flushb) {
struct nlmsghdr *fn;
if (NLMSG_ALIGN(G_filter.flushp) + n->nlmsg_len > G_filter.flushe) {
if (flush_update())
return -1;
}
fn = (struct nlmsghdr*)(G_filter.flushb + NLMSG_ALIGN(G_filter.flushp));
memcpy(fn, n, n->nlmsg_len);
fn->nlmsg_type = RTM_DELADDR;
fn->nlmsg_flags = NLM_F_REQUEST;
fn->nlmsg_seq = ++G_filter.rth->seq;
G_filter.flushp = (((char*)fn) + n->nlmsg_len) - G_filter.flushb;
G_filter.flushed = 1;
return 0;
}
if (n->nlmsg_type == RTM_DELADDR)
printf("Deleted ");
if (G_filter.oneline)
printf("%u: %s", ifa->ifa_index, ll_index_to_name(ifa->ifa_index));
if (ifa->ifa_family == AF_INET)
printf(" inet ");
else if (ifa->ifa_family == AF_INET6)
printf(" inet6 ");
else
printf(" family %d ", ifa->ifa_family);
if (rta_tb[IFA_LOCAL]) {
fputs(rt_addr_n2a(ifa->ifa_family, RTA_DATA(rta_tb[IFA_LOCAL])),
stdout
);
if (rta_tb[IFA_ADDRESS] == NULL
|| memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), 4) == 0
) {
printf("/%d ", ifa->ifa_prefixlen);
} else {
printf(" peer %s/%d ",
rt_addr_n2a(ifa->ifa_family, RTA_DATA(rta_tb[IFA_ADDRESS])),
ifa->ifa_prefixlen
);
}
}
if (rta_tb[IFA_BROADCAST]) {
printf("brd %s ",
rt_addr_n2a(ifa->ifa_family,
RTA_DATA(rta_tb[IFA_BROADCAST]))
);
}
if (rta_tb[IFA_ANYCAST]) {
printf("any %s ",
rt_addr_n2a(ifa->ifa_family,
RTA_DATA(rta_tb[IFA_ANYCAST]))
);
}
printf("scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope));
if (ifa->ifa_flags & IFA_F_SECONDARY) {
ifa->ifa_flags &= ~IFA_F_SECONDARY;
printf("secondary ");
}
if (ifa->ifa_flags & IFA_F_TENTATIVE) {
ifa->ifa_flags &= ~IFA_F_TENTATIVE;
printf("tentative ");
}
if (ifa->ifa_flags & IFA_F_DADFAILED) {
ifa->ifa_flags &= ~IFA_F_DADFAILED;
printf("dadfailed ");
}
if (ifa->ifa_flags & IFA_F_DEPRECATED) {
ifa->ifa_flags &= ~IFA_F_DEPRECATED;
printf("deprecated ");
}
if (!(ifa->ifa_flags & IFA_F_PERMANENT)) {
printf("dynamic ");
} else
ifa->ifa_flags &= ~IFA_F_PERMANENT;
if (ifa->ifa_flags)
printf("flags %02x ", ifa->ifa_flags);
if (rta_tb[IFA_LABEL])
fputs((char*)RTA_DATA(rta_tb[IFA_LABEL]), stdout);
if (rta_tb[IFA_CACHEINFO]) {
struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]);
char buf[128];
bb_putchar(_SL_);
if (ci->ifa_valid == 0xFFFFFFFFU)
sprintf(buf, "valid_lft forever");
else
sprintf(buf, "valid_lft %dsec", ci->ifa_valid);
if (ci->ifa_prefered == 0xFFFFFFFFU)
sprintf(buf+strlen(buf), " preferred_lft forever");
else
sprintf(buf+strlen(buf), " preferred_lft %dsec", ci->ifa_prefered);
printf(" %s", buf);
}
bb_putchar('\n');
/*fflush_all();*/
return 0;
}
struct nlmsg_list {
struct nlmsg_list *next;
struct nlmsghdr h;
};
static int print_selected_addrinfo(int ifindex, struct nlmsg_list *ainfo)
{
for (; ainfo; ainfo = ainfo->next) {
struct nlmsghdr *n = &ainfo->h;
struct ifaddrmsg *ifa = NLMSG_DATA(n);
if (n->nlmsg_type != RTM_NEWADDR)
continue;
if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifa)))
return -1;
if (ifa->ifa_index != ifindex
|| (G_filter.family && G_filter.family != ifa->ifa_family)
) {
continue;
}
print_addrinfo(NULL, n, NULL);
}
return 0;
}
static int FAST_FUNC store_nlmsg(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
{
struct nlmsg_list **linfo = (struct nlmsg_list**)arg;
struct nlmsg_list *h;
struct nlmsg_list **lp;
h = xzalloc(n->nlmsg_len + sizeof(void*));
memcpy(&h->h, n, n->nlmsg_len);
/*h->next = NULL; - xzalloc did it */
for (lp = linfo; *lp; lp = &(*lp)->next)
continue;
*lp = h;
ll_remember_index(who, n, NULL);
return 0;
}
static void ipaddr_reset_filter(int _oneline)
{
memset(&G_filter, 0, sizeof(G_filter));
G_filter.oneline = _oneline;
}
/* Return value becomes exitcode. It's okay to not return at all */
int FAST_FUNC ipaddr_list_or_flush(char **argv, int flush)
{
static const char option[] ALIGN1 = "to\0""scope\0""up\0""label\0""dev\0";
struct nlmsg_list *linfo = NULL;
struct nlmsg_list *ainfo = NULL;
struct nlmsg_list *l;
struct rtnl_handle rth;
char *filter_dev = NULL;
ipaddr_reset_filter(oneline);
G_filter.showqueue = 1;
if (G_filter.family == AF_UNSPEC)
G_filter.family = preferred_family;
if (flush) {
if (!*argv) {
bb_error_msg_and_die(bb_msg_requires_arg, "flush");
}
if (G_filter.family == AF_PACKET) {
bb_simple_error_msg_and_die("can't flush link addresses");
}
}
while (*argv) {
const smalluint key = index_in_strings(option, *argv);
if (key == 0) { /* to */
NEXT_ARG();
get_prefix(&G_filter.pfx, *argv, G_filter.family);
if (G_filter.family == AF_UNSPEC) {
G_filter.family = G_filter.pfx.family;
}
} else if (key == 1) { /* scope */
uint32_t scope = 0;
NEXT_ARG();
G_filter.scopemask = -1;
if (rtnl_rtscope_a2n(&scope, *argv)) {
if (strcmp(*argv, "all") != 0) {
invarg_1_to_2(*argv, "scope");
}
scope = RT_SCOPE_NOWHERE;
G_filter.scopemask = 0;
}
G_filter.scope = scope;
} else if (key == 2) { /* up */
G_filter.up = 1;
} else if (key == 3) { /* label */
NEXT_ARG();
G_filter.label = *argv;
} else {
if (key == 4) /* dev */
NEXT_ARG();
if (filter_dev)
duparg2("dev", *argv);
filter_dev = *argv;
}
argv++;
}
xrtnl_open(&rth);
xrtnl_wilddump_request(&rth, preferred_family, RTM_GETLINK);
xrtnl_dump_filter(&rth, store_nlmsg, &linfo);
if (filter_dev) {
G_filter.ifindex = xll_name_to_index(filter_dev);
}
if (flush) {
char flushb[4096-512];
G_filter.flushb = flushb;
G_filter.flushp = 0;
G_filter.flushe = sizeof(flushb);
G_filter.rth = &rth;
for (;;) {
xrtnl_wilddump_request(&rth, G_filter.family, RTM_GETADDR);
G_filter.flushed = 0;
xrtnl_dump_filter(&rth, print_addrinfo, NULL);
if (G_filter.flushed == 0) {
return 0;
}
if (flush_update() < 0) {
return 1;
}
}
}
if (G_filter.family != AF_PACKET) {
xrtnl_wilddump_request(&rth, G_filter.family, RTM_GETADDR);
xrtnl_dump_filter(&rth, store_nlmsg, &ainfo);
}
if (G_filter.family && G_filter.family != AF_PACKET) {
struct nlmsg_list **lp;
lp = &linfo;
while ((l = *lp) != NULL) {
int ok = 0;
struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
struct nlmsg_list *a;
for (a = ainfo; a; a = a->next) {
struct nlmsghdr *n = &a->h;
struct ifaddrmsg *ifa = NLMSG_DATA(n);
if (ifa->ifa_index != ifi->ifi_index
|| (G_filter.family && G_filter.family != ifa->ifa_family)
) {
continue;
}
if ((G_filter.scope ^ ifa->ifa_scope) & G_filter.scopemask)
continue;
if ((G_filter.flags ^ ifa->ifa_flags) & G_filter.flagmask)
continue;
if (G_filter.pfx.family || G_filter.label) {
struct rtattr *tb[IFA_MAX+1];
//memset(tb, 0, sizeof(tb)); - parse_rtattr does this
parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(n));
if (!tb[IFA_LOCAL])
tb[IFA_LOCAL] = tb[IFA_ADDRESS];
if (G_filter.pfx.family && tb[IFA_LOCAL]) {
inet_prefix dst;
memset(&dst, 0, sizeof(dst));
dst.family = ifa->ifa_family;
memcpy(&dst.data, RTA_DATA(tb[IFA_LOCAL]), RTA_PAYLOAD(tb[IFA_LOCAL]));
if (inet_addr_match(&dst, &G_filter.pfx, G_filter.pfx.bitlen))
continue;
}
if (G_filter.label) {
const char *label;
if (tb[IFA_LABEL])
label = RTA_DATA(tb[IFA_LABEL]);
else
label = ll_index_to_name(ifa->ifa_index);
if (fnmatch(G_filter.label, label, 0) != 0)
continue;
}
}
ok = 1;
break;
}
if (!ok)
*lp = l->next;
else
lp = &l->next;
}
}
for (l = linfo; l; l = l->next) {
if ((oneline && G_filter.family != AF_PACKET)
/* ^^^^^^^^^ "ip -oneline a" does not print link info */
|| (print_linkinfo(&l->h) == 0)
) {
struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
if (G_filter.family != AF_PACKET)
print_selected_addrinfo(ifi->ifi_index, ainfo);
}
}
return 0;
}
static int default_scope(inet_prefix *lcl)
{
if (lcl->family == AF_INET) {
if (lcl->bytelen >= 1 && *(uint8_t*)&lcl->data == 127)
return RT_SCOPE_HOST;
}
return 0;
}
/* Return value becomes exitcode. It's okay to not return at all */
static int ipaddr_modify(int cmd, int flags, char **argv)
{
/* If you add stuff here, update ipaddr_full_usage */
static const char option[] ALIGN1 =
"peer\0""remote\0""broadcast\0""brd\0"
"anycast\0""scope\0""dev\0""label\0""local\0";
#define option_peer option
#define option_broadcast (option + sizeof("peer") + sizeof("remote"))
#define option_anycast (option_broadcast + sizeof("broadcast") + sizeof("brd"))
struct rtnl_handle rth;
struct {
struct nlmsghdr n;
struct ifaddrmsg ifa;
char buf[256];
} req;
char *d = NULL;
char *l = NULL;
inet_prefix lcl;
inet_prefix peer;
int local_len = 0;
int peer_len = 0;
int brd_len = 0;
int any_len = 0;
bool scoped = 0;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
req.n.nlmsg_flags = NLM_F_REQUEST | flags;
req.n.nlmsg_type = cmd;
req.ifa.ifa_family = preferred_family;
while (*argv) {
unsigned arg = index_in_strings(option, *argv);
/* if search fails, "local" is assumed */
if ((int)arg >= 0)
NEXT_ARG();
if (arg <= 1) { /* peer, remote */
if (peer_len) {
duparg(option_peer, *argv);
}
get_prefix(&peer, *argv, req.ifa.ifa_family);
peer_len = peer.bytelen;
if (req.ifa.ifa_family == AF_UNSPEC) {
req.ifa.ifa_family = peer.family;
}
addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &peer.data, peer.bytelen);
req.ifa.ifa_prefixlen = peer.bitlen;
} else if (arg <= 3) { /* broadcast, brd */
inet_prefix addr;
if (brd_len) {
duparg(option_broadcast, *argv);
}
if (LONE_CHAR(*argv, '+')) {
brd_len = -1;
} else if (LONE_DASH(*argv)) {
brd_len = -2;
} else {
get_addr(&addr, *argv, req.ifa.ifa_family);
if (req.ifa.ifa_family == AF_UNSPEC)
req.ifa.ifa_family = addr.family;
addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &addr.data, addr.bytelen);
brd_len = addr.bytelen;
}
} else if (arg == 4) { /* anycast */
inet_prefix addr;
if (any_len) {
duparg(option_anycast, *argv);
}
get_addr(&addr, *argv, req.ifa.ifa_family);
if (req.ifa.ifa_family == AF_UNSPEC) {
req.ifa.ifa_family = addr.family;
}
addattr_l(&req.n, sizeof(req), IFA_ANYCAST, &addr.data, addr.bytelen);
any_len = addr.bytelen;
} else if (arg == 5) { /* scope */
uint32_t scope = 0;
if (rtnl_rtscope_a2n(&scope, *argv)) {
invarg_1_to_2(*argv, "scope");
}
req.ifa.ifa_scope = scope;
scoped = 1;
} else if (arg == 6) { /* dev */
d = *argv;
} else if (arg == 7) { /* label */
l = *argv;
addattr_l(&req.n, sizeof(req), IFA_LABEL, l, strlen(l) + 1);
} else {
/* local (specified or assumed) */
if (local_len) {
duparg2("local", *argv);
}
get_prefix(&lcl, *argv, req.ifa.ifa_family);
if (req.ifa.ifa_family == AF_UNSPEC) {
req.ifa.ifa_family = lcl.family;
}
addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen);
local_len = lcl.bytelen;
}
argv++;
}
if (!d) {
/* There was no "dev IFACE", but we need that */
bb_simple_error_msg_and_die("need \"dev IFACE\"");
}
if (l && !is_prefixed_with(l, d)) {
bb_error_msg_and_die("\"dev\" (%s) must match \"label\" (%s)", d, l);
}
if (peer_len == 0 && local_len && cmd != RTM_DELADDR) {
peer = lcl;
addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &lcl.data, lcl.bytelen);
}
if (req.ifa.ifa_prefixlen == 0)
req.ifa.ifa_prefixlen = lcl.bitlen;
if (brd_len < 0 && cmd != RTM_DELADDR) {
inet_prefix brd;
int i;
if (req.ifa.ifa_family != AF_INET) {
bb_simple_error_msg_and_die("broadcast can be set only for IPv4 addresses");
}
brd = peer;
if (brd.bitlen <= 30) {
for (i = 31; i >= brd.bitlen; i--) {
if (brd_len == -1)
brd.data[0] |= htonl(1<<(31-i));
else
brd.data[0] &= ~htonl(1<<(31-i));
}
addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &brd.data, brd.bytelen);
brd_len = brd.bytelen;
}
}
if (!scoped && cmd != RTM_DELADDR)
req.ifa.ifa_scope = default_scope(&lcl);
xrtnl_open(&rth);
ll_init_map(&rth);
req.ifa.ifa_index = xll_name_to_index(d);
if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
return 2;
return 0;
}
/* Return value becomes exitcode. It's okay to not return at all */
int FAST_FUNC do_ipaddr(char **argv)
{
static const char commands[] ALIGN1 =
/* 0 1 2 3 4 5 6 7 8 */
"add\0""change\0""chg\0""replace\0""delete\0""list\0""show\0""lst\0""flush\0";
int cmd = 2;
INIT_G();
if (*argv) {
cmd = index_in_substrings(commands, *argv);
if (cmd < 0)
invarg_1_to_2(*argv, applet_name);
argv++;
if (cmd <= 4) {
return ipaddr_modify(
/*cmd:*/ cmd == 4 ? RTM_DELADDR : RTM_NEWADDR,
/*flags:*/
cmd == 0 ? NLM_F_CREATE|NLM_F_EXCL : /* add */
cmd == 1 || cmd == 2 ? NLM_F_REPLACE : /* change */
cmd == 3 ? NLM_F_CREATE|NLM_F_REPLACE : /* replace */
0 /* delete */
, argv);
}
}
return ipaddr_list_or_flush(argv, cmd == 8);
}