iproute2/ip/ipstats.c
Beniamino Galvani 554ea3649d ip: do not print stray prefixes in monitor mode
When running "ip monitor", accept_msg() first prints the prefix and
then calls the object-specific print function, which also does the
filtering. Therefore, it is possible that the prefix is printed even
for events that get ignored later. For example:

  ip link add dummy1 type dummy
  ip link set dummy1 up
  ip -ts monitor all dev dummy1 &
  ip link add dummy2 type dummy
  ip addr add dev dummy1 192.0.2.1/24

generates:

  [2024-07-12T22:11:26.338342] [LINK][2024-07-12T22:11:26.339846] [ADDR]314: dummy1    inet 192.0.2.1/24 scope global dummy1
       valid_lft forever preferred_lft forever

Fix this by printing the prefix only after the filtering. Now the
output for the commands above is:

 [2024-07-12T22:11:26.339846] [ADDR]314: dummy1    inet 192.0.2.1/24 scope global dummy1
       valid_lft forever preferred_lft forever

See also commit 7e0a889b54 ("bridge: Do not print stray prefixes in
monitor mode") which fixed the same problem in the bridge tool.

Signed-off-by: Beniamino Galvani <b.galvani@gmail.com>
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
2024-07-17 16:48:25 -07:00

1364 lines
32 KiB
C

// SPDX-License-Identifier: GPL-2.0+
#include <alloca.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include "list.h"
#include "utils.h"
#include "ip_common.h"
struct ipstats_stat_dump_filters {
/* mask[0] filters outer attributes. Then individual nests have their
* filtering mask at the index of the nested attribute.
*/
__u32 mask[IFLA_STATS_MAX + 1];
};
static void
ipstats_stat_desc_enable_bit(struct ipstats_stat_dump_filters *filters,
unsigned int group, unsigned int subgroup)
{
filters->mask[0] |= IFLA_STATS_FILTER_BIT(group);
if (subgroup)
filters->mask[group] |= IFLA_STATS_FILTER_BIT(subgroup);
}
struct ipstats_stat_show_attrs {
struct if_stats_msg *ifsm;
int len;
/* tbs[0] contains top-level attribute table. Then individual nests have
* their attribute tables at the index of the nested attribute.
*/
struct rtattr **tbs[IFLA_STATS_MAX + 1];
};
static const char *const ipstats_levels[] = {
"group",
"subgroup",
"suite",
};
enum {
IPSTATS_LEVELS_COUNT = ARRAY_SIZE(ipstats_levels),
};
struct ipstats_sel {
const char *sel[IPSTATS_LEVELS_COUNT];
};
struct ipstats_stat_enabled_one {
const struct ipstats_stat_desc *desc;
struct ipstats_sel sel;
};
struct ipstats_stat_enabled {
struct ipstats_stat_enabled_one *enabled;
size_t nenabled;
};
static const unsigned int ipstats_stat_ifla_max[] = {
[0] = IFLA_STATS_MAX,
[IFLA_STATS_LINK_XSTATS] = LINK_XSTATS_TYPE_MAX,
[IFLA_STATS_LINK_XSTATS_SLAVE] = LINK_XSTATS_TYPE_MAX,
[IFLA_STATS_LINK_OFFLOAD_XSTATS] = IFLA_OFFLOAD_XSTATS_MAX,
[IFLA_STATS_AF_SPEC] = AF_MAX - 1,
};
static_assert(ARRAY_SIZE(ipstats_stat_ifla_max) == IFLA_STATS_MAX + 1,
"An IFLA_STATS attribute is missing from the ifla_max table");
static int
ipstats_stat_show_attrs_alloc_tb(struct ipstats_stat_show_attrs *attrs,
unsigned int group)
{
unsigned int ifla_max;
int err;
assert(group < ARRAY_SIZE(ipstats_stat_ifla_max));
assert(group < ARRAY_SIZE(attrs->tbs));
ifla_max = ipstats_stat_ifla_max[group];
assert(ifla_max != 0);
if (attrs->tbs[group])
return 0;
attrs->tbs[group] = calloc(ifla_max + 1, sizeof(*attrs->tbs[group]));
if (attrs->tbs[group] == NULL) {
fprintf(stderr, "Error parsing netlink answer: %s\n",
strerror(errno));
return -errno;
}
if (group == 0)
err = parse_rtattr(attrs->tbs[group], ifla_max,
IFLA_STATS_RTA(attrs->ifsm), attrs->len);
else
err = parse_rtattr_nested(attrs->tbs[group], ifla_max,
attrs->tbs[0][group]);
if (err != 0) {
free(attrs->tbs[group]);
attrs->tbs[group] = NULL;
}
return err;
}
static const struct rtattr *
ipstats_stat_show_get_attr(struct ipstats_stat_show_attrs *attrs,
int group, int subgroup, int *err)
{
int tmp_err;
if (err == NULL)
err = &tmp_err;
*err = 0;
if (subgroup == 0)
return attrs->tbs[0][group];
if (attrs->tbs[0][group] == NULL)
return NULL;
*err = ipstats_stat_show_attrs_alloc_tb(attrs, group);
if (*err != 0)
return NULL;
return attrs->tbs[group][subgroup];
}
static void
ipstats_stat_show_attrs_free(struct ipstats_stat_show_attrs *attrs)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(attrs->tbs); i++)
free(attrs->tbs[i]);
}
#define IPSTATS_RTA_PAYLOAD(VAR, AT) \
do { \
const struct rtattr *__at = (AT); \
size_t __at_sz = __at->rta_len - RTA_LENGTH(0); \
size_t __var_sz = sizeof(VAR); \
typeof(VAR) *__dest = &VAR; \
\
memset(__dest, 0, __var_sz); \
memcpy(__dest, RTA_DATA(__at), MIN(__at_sz, __var_sz)); \
} while (0)
static int ipstats_show_64(struct ipstats_stat_show_attrs *attrs,
unsigned int group, unsigned int subgroup)
{
struct rtnl_link_stats64 stats;
const struct rtattr *at;
int err;
at = ipstats_stat_show_get_attr(attrs, group, subgroup, &err);
if (at == NULL)
return err;
IPSTATS_RTA_PAYLOAD(stats, at);
open_json_object("stats64");
print_stats64(stdout, &stats, NULL, NULL);
close_json_object();
return 0;
}
static void print_hw_stats64(FILE *fp, struct rtnl_hw_stats64 *s)
{
unsigned int cols[] = {
strlen("*X: bytes"),
strlen("packets"),
strlen("errors"),
strlen("dropped"),
strlen("overrun"),
};
if (is_json_context()) {
/* RX stats */
open_json_object("rx");
print_u64(PRINT_JSON, "bytes", NULL, s->rx_bytes);
print_u64(PRINT_JSON, "packets", NULL, s->rx_packets);
print_u64(PRINT_JSON, "errors", NULL, s->rx_errors);
print_u64(PRINT_JSON, "dropped", NULL, s->rx_dropped);
print_u64(PRINT_JSON, "multicast", NULL, s->multicast);
close_json_object();
/* TX stats */
open_json_object("tx");
print_u64(PRINT_JSON, "bytes", NULL, s->tx_bytes);
print_u64(PRINT_JSON, "packets", NULL, s->tx_packets);
print_u64(PRINT_JSON, "errors", NULL, s->tx_errors);
print_u64(PRINT_JSON, "dropped", NULL, s->tx_dropped);
close_json_object();
} else {
size_columns(cols, ARRAY_SIZE(cols),
s->rx_bytes, s->rx_packets, s->rx_errors,
s->rx_dropped, s->multicast);
size_columns(cols, ARRAY_SIZE(cols),
s->tx_bytes, s->tx_packets, s->tx_errors,
s->tx_dropped, 0);
/* RX stats */
fprintf(fp, " RX: %*s %*s %*s %*s %*s%s",
cols[0] - 4, "bytes", cols[1], "packets",
cols[2], "errors", cols[3], "dropped",
cols[4], "mcast", _SL_);
fprintf(fp, " ");
print_num(fp, cols[0], s->rx_bytes);
print_num(fp, cols[1], s->rx_packets);
print_num(fp, cols[2], s->rx_errors);
print_num(fp, cols[3], s->rx_dropped);
print_num(fp, cols[4], s->multicast);
fprintf(fp, "%s", _SL_);
/* TX stats */
fprintf(fp, " TX: %*s %*s %*s %*s%s",
cols[0] - 4, "bytes", cols[1], "packets",
cols[2], "errors", cols[3], "dropped", _SL_);
fprintf(fp, " ");
print_num(fp, cols[0], s->tx_bytes);
print_num(fp, cols[1], s->tx_packets);
print_num(fp, cols[2], s->tx_errors);
print_num(fp, cols[3], s->tx_dropped);
}
}
static int ipstats_show_hw64(const struct rtattr *at)
{
struct rtnl_hw_stats64 stats;
IPSTATS_RTA_PAYLOAD(stats, at);
print_hw_stats64(stdout, &stats);
return 0;
}
enum ipstats_maybe_on_off {
IPSTATS_MOO_OFF = -1,
IPSTATS_MOO_INVALID,
IPSTATS_MOO_ON,
};
static bool ipstats_moo_to_bool(enum ipstats_maybe_on_off moo)
{
assert(moo != IPSTATS_MOO_INVALID);
return moo + 1;
}
static int ipstats_print_moo(enum output_type t, const char *key,
const char *fmt, enum ipstats_maybe_on_off moo)
{
if (!moo)
return 0;
return print_on_off(t, key, fmt, ipstats_moo_to_bool(moo));
}
struct ipstats_hw_s_info_one {
enum ipstats_maybe_on_off request;
enum ipstats_maybe_on_off used;
};
enum ipstats_hw_s_info_idx {
IPSTATS_HW_S_INFO_IDX_L3_STATS,
IPSTATS_HW_S_INFO_IDX_COUNT
};
static const char *const ipstats_hw_s_info_name[] = {
"l3_stats",
};
static_assert(ARRAY_SIZE(ipstats_hw_s_info_name) ==
IPSTATS_HW_S_INFO_IDX_COUNT,
"mismatch: enum ipstats_hw_s_info_idx x ipstats_hw_s_info_name");
struct ipstats_hw_s_info {
/* Indexed by enum ipstats_hw_s_info_idx. */
struct ipstats_hw_s_info_one *infos[IPSTATS_HW_S_INFO_IDX_COUNT];
};
static enum ipstats_maybe_on_off ipstats_dissect_01(int value, const char *what)
{
switch (value) {
case 0:
return IPSTATS_MOO_OFF;
case 1:
return IPSTATS_MOO_ON;
default:
fprintf(stderr, "Invalid value for %s: expected 0 or 1, got %d.\n",
what, value);
return IPSTATS_MOO_INVALID;
}
}
static int ipstats_dissect_hw_s_info_one(const struct rtattr *at,
struct ipstats_hw_s_info_one *p_hwsio,
const char *what)
{
int attr_id_request = IFLA_OFFLOAD_XSTATS_HW_S_INFO_REQUEST;
struct rtattr *tb[IFLA_OFFLOAD_XSTATS_HW_S_INFO_MAX + 1];
int attr_id_used = IFLA_OFFLOAD_XSTATS_HW_S_INFO_USED;
struct ipstats_hw_s_info_one hwsio = {};
int err;
int v;
err = parse_rtattr_nested(tb, IFLA_OFFLOAD_XSTATS_HW_S_INFO_MAX, at);
if (err)
return err;
if (tb[attr_id_request]) {
v = rta_getattr_u8(tb[attr_id_request]);
hwsio.request = ipstats_dissect_01(v, "request");
/* This has to be present & valid. */
if (!hwsio.request)
return -EINVAL;
}
if (tb[attr_id_used]) {
v = rta_getattr_u8(tb[attr_id_used]);
hwsio.used = ipstats_dissect_01(v, "used");
}
*p_hwsio = hwsio;
return 0;
}
static int ipstats_dissect_hw_s_info(const struct rtattr *at,
struct ipstats_hw_s_info *hwsi)
{
struct rtattr *tb[IFLA_OFFLOAD_XSTATS_MAX + 1];
int attr_id_l3 = IFLA_OFFLOAD_XSTATS_L3_STATS;
struct ipstats_hw_s_info_one *hwsio = NULL;
int err;
err = parse_rtattr_nested(tb, IFLA_OFFLOAD_XSTATS_MAX, at);
if (err)
return err;
*hwsi = (struct ipstats_hw_s_info){};
if (tb[attr_id_l3]) {
hwsio = malloc(sizeof(*hwsio));
if (!hwsio) {
err = -ENOMEM;
goto out;
}
err = ipstats_dissect_hw_s_info_one(tb[attr_id_l3], hwsio, "l3");
if (err)
goto out;
hwsi->infos[IPSTATS_HW_S_INFO_IDX_L3_STATS] = hwsio;
hwsio = NULL;
}
return 0;
out:
free(hwsio);
return err;
}
static void ipstats_fini_hw_s_info(struct ipstats_hw_s_info *hwsi)
{
int i;
for (i = 0; i < IPSTATS_HW_S_INFO_IDX_COUNT; i++)
free(hwsi->infos[i]);
}
static void
__ipstats_show_hw_s_info_one(const struct ipstats_hw_s_info_one *hwsio)
{
if (hwsio == NULL)
return;
ipstats_print_moo(PRINT_ANY, "request", " %s", hwsio->request);
ipstats_print_moo(PRINT_ANY, "used", " used %s", hwsio->used);
}
static void
ipstats_show_hw_s_info_one(const struct ipstats_hw_s_info *hwsi,
enum ipstats_hw_s_info_idx idx)
{
const struct ipstats_hw_s_info_one *hwsio = hwsi->infos[idx];
const char *name = ipstats_hw_s_info_name[idx];
if (hwsio == NULL)
return;
print_string(PRINT_FP, NULL, " %s", name);
open_json_object(name);
__ipstats_show_hw_s_info_one(hwsio);
close_json_object();
}
static int __ipstats_show_hw_s_info(const struct rtattr *at)
{
struct ipstats_hw_s_info hwsi = {};
int err;
err = ipstats_dissect_hw_s_info(at, &hwsi);
if (err)
return err;
open_json_object("info");
ipstats_show_hw_s_info_one(&hwsi, IPSTATS_HW_S_INFO_IDX_L3_STATS);
close_json_object();
ipstats_fini_hw_s_info(&hwsi);
return 0;
}
static int ipstats_show_hw_s_info(struct ipstats_stat_show_attrs *attrs,
unsigned int group, unsigned int subgroup)
{
const struct rtattr *at;
int err;
at = ipstats_stat_show_get_attr(attrs, group, subgroup, &err);
if (at == NULL)
return err;
print_nl();
return __ipstats_show_hw_s_info(at);
}
static int __ipstats_show_hw_stats(const struct rtattr *at_hwsi,
const struct rtattr *at_stats,
enum ipstats_hw_s_info_idx idx)
{
int err = 0;
if (at_hwsi != NULL) {
struct ipstats_hw_s_info hwsi = {};
err = ipstats_dissect_hw_s_info(at_hwsi, &hwsi);
if (err)
return err;
open_json_object("info");
__ipstats_show_hw_s_info_one(hwsi.infos[idx]);
close_json_object();
ipstats_fini_hw_s_info(&hwsi);
}
if (at_stats != NULL) {
print_nl();
open_json_object("stats64");
err = ipstats_show_hw64(at_stats);
close_json_object();
}
return err;
}
static int ipstats_show_hw_stats(struct ipstats_stat_show_attrs *attrs,
unsigned int group,
unsigned int hw_s_info,
unsigned int hw_stats,
enum ipstats_hw_s_info_idx idx)
{
const struct rtattr *at_stats;
const struct rtattr *at_hwsi;
int err = 0;
at_hwsi = ipstats_stat_show_get_attr(attrs, group, hw_s_info, &err);
if (at_hwsi == NULL)
return err;
at_stats = ipstats_stat_show_get_attr(attrs, group, hw_stats, &err);
if (at_stats == NULL && err != 0)
return err;
return __ipstats_show_hw_stats(at_hwsi, at_stats, idx);
}
static void
ipstats_stat_desc_pack_cpu_hit(struct ipstats_stat_dump_filters *filters,
const struct ipstats_stat_desc *desc)
{
ipstats_stat_desc_enable_bit(filters,
IFLA_STATS_LINK_OFFLOAD_XSTATS,
IFLA_OFFLOAD_XSTATS_CPU_HIT);
}
static int ipstats_stat_desc_show_cpu_hit(struct ipstats_stat_show_attrs *attrs,
const struct ipstats_stat_desc *desc)
{
print_nl();
return ipstats_show_64(attrs,
IFLA_STATS_LINK_OFFLOAD_XSTATS,
IFLA_OFFLOAD_XSTATS_CPU_HIT);
}
static const struct ipstats_stat_desc ipstats_stat_desc_offload_cpu_hit = {
.name = "cpu_hit",
.kind = IPSTATS_STAT_DESC_KIND_LEAF,
.pack = &ipstats_stat_desc_pack_cpu_hit,
.show = &ipstats_stat_desc_show_cpu_hit,
};
static void
ipstats_stat_desc_pack_hw_stats_info(struct ipstats_stat_dump_filters *filters,
const struct ipstats_stat_desc *desc)
{
ipstats_stat_desc_enable_bit(filters,
IFLA_STATS_LINK_OFFLOAD_XSTATS,
IFLA_OFFLOAD_XSTATS_HW_S_INFO);
}
static int
ipstats_stat_desc_show_hw_stats_info(struct ipstats_stat_show_attrs *attrs,
const struct ipstats_stat_desc *desc)
{
return ipstats_show_hw_s_info(attrs,
IFLA_STATS_LINK_OFFLOAD_XSTATS,
IFLA_OFFLOAD_XSTATS_HW_S_INFO);
}
static const struct ipstats_stat_desc ipstats_stat_desc_offload_hw_s_info = {
.name = "hw_stats_info",
.kind = IPSTATS_STAT_DESC_KIND_LEAF,
.pack = &ipstats_stat_desc_pack_hw_stats_info,
.show = &ipstats_stat_desc_show_hw_stats_info,
};
static void
ipstats_stat_desc_pack_l3_stats(struct ipstats_stat_dump_filters *filters,
const struct ipstats_stat_desc *desc)
{
ipstats_stat_desc_enable_bit(filters,
IFLA_STATS_LINK_OFFLOAD_XSTATS,
IFLA_OFFLOAD_XSTATS_L3_STATS);
ipstats_stat_desc_enable_bit(filters,
IFLA_STATS_LINK_OFFLOAD_XSTATS,
IFLA_OFFLOAD_XSTATS_HW_S_INFO);
}
static int
ipstats_stat_desc_show_l3_stats(struct ipstats_stat_show_attrs *attrs,
const struct ipstats_stat_desc *desc)
{
return ipstats_show_hw_stats(attrs,
IFLA_STATS_LINK_OFFLOAD_XSTATS,
IFLA_OFFLOAD_XSTATS_HW_S_INFO,
IFLA_OFFLOAD_XSTATS_L3_STATS,
IPSTATS_HW_S_INFO_IDX_L3_STATS);
}
static const struct ipstats_stat_desc ipstats_stat_desc_offload_l3_stats = {
.name = "l3_stats",
.kind = IPSTATS_STAT_DESC_KIND_LEAF,
.pack = &ipstats_stat_desc_pack_l3_stats,
.show = &ipstats_stat_desc_show_l3_stats,
};
static const struct ipstats_stat_desc *ipstats_stat_desc_offload_subs[] = {
&ipstats_stat_desc_offload_cpu_hit,
&ipstats_stat_desc_offload_hw_s_info,
&ipstats_stat_desc_offload_l3_stats,
};
static const struct ipstats_stat_desc ipstats_stat_desc_offload_group = {
.name = "offload",
.kind = IPSTATS_STAT_DESC_KIND_GROUP,
.subs = ipstats_stat_desc_offload_subs,
.nsubs = ARRAY_SIZE(ipstats_stat_desc_offload_subs),
};
void ipstats_stat_desc_pack_xstats(struct ipstats_stat_dump_filters *filters,
const struct ipstats_stat_desc *desc)
{
struct ipstats_stat_desc_xstats *xdesc;
xdesc = container_of(desc, struct ipstats_stat_desc_xstats, desc);
ipstats_stat_desc_enable_bit(filters, xdesc->xstats_at, 0);
}
int ipstats_stat_desc_show_xstats(struct ipstats_stat_show_attrs *attrs,
const struct ipstats_stat_desc *desc)
{
struct ipstats_stat_desc_xstats *xdesc;
const struct rtattr *at;
struct rtattr **tb;
int err;
xdesc = container_of(desc, struct ipstats_stat_desc_xstats, desc);
at = ipstats_stat_show_get_attr(attrs,
xdesc->xstats_at,
xdesc->link_type_at, &err);
if (at == NULL)
return err;
tb = alloca(sizeof(*tb) * (xdesc->inner_max + 1));
err = parse_rtattr_nested(tb, xdesc->inner_max, at);
if (err != 0)
return err;
if (tb[xdesc->inner_at] != NULL) {
print_nl();
xdesc->show_cb(tb[xdesc->inner_at]);
}
return 0;
}
static const struct ipstats_stat_desc *ipstats_stat_desc_xstats_subs[] = {
&ipstats_stat_desc_xstats_bridge_group,
&ipstats_stat_desc_xstats_bond_group,
};
static const struct ipstats_stat_desc ipstats_stat_desc_xstats_group = {
.name = "xstats",
.kind = IPSTATS_STAT_DESC_KIND_GROUP,
.subs = ipstats_stat_desc_xstats_subs,
.nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_subs),
};
static const struct ipstats_stat_desc *ipstats_stat_desc_xstats_slave_subs[] = {
&ipstats_stat_desc_xstats_slave_bridge_group,
&ipstats_stat_desc_xstats_slave_bond_group,
};
static const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_group = {
.name = "xstats_slave",
.kind = IPSTATS_STAT_DESC_KIND_GROUP,
.subs = ipstats_stat_desc_xstats_slave_subs,
.nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_slave_subs),
};
static void
ipstats_stat_desc_pack_link(struct ipstats_stat_dump_filters *filters,
const struct ipstats_stat_desc *desc)
{
ipstats_stat_desc_enable_bit(filters,
IFLA_STATS_LINK_64, 0);
}
static int
ipstats_stat_desc_show_link(struct ipstats_stat_show_attrs *attrs,
const struct ipstats_stat_desc *desc)
{
print_nl();
return ipstats_show_64(attrs, IFLA_STATS_LINK_64, 0);
}
static const struct ipstats_stat_desc ipstats_stat_desc_toplev_link = {
.name = "link",
.kind = IPSTATS_STAT_DESC_KIND_LEAF,
.pack = &ipstats_stat_desc_pack_link,
.show = &ipstats_stat_desc_show_link,
};
static const struct ipstats_stat_desc ipstats_stat_desc_afstats_group;
static void
ipstats_stat_desc_pack_afstats(struct ipstats_stat_dump_filters *filters,
const struct ipstats_stat_desc *desc)
{
ipstats_stat_desc_enable_bit(filters, IFLA_STATS_AF_SPEC, 0);
}
static int
ipstats_stat_desc_show_afstats_mpls(struct ipstats_stat_show_attrs *attrs,
const struct ipstats_stat_desc *desc)
{
struct rtattr *mrtb[MPLS_STATS_MAX+1];
struct mpls_link_stats stats;
const struct rtattr *at;
int err;
at = ipstats_stat_show_get_attr(attrs, IFLA_STATS_AF_SPEC,
AF_MPLS, &err);
if (at == NULL)
return err;
parse_rtattr_nested(mrtb, MPLS_STATS_MAX, at);
if (mrtb[MPLS_STATS_LINK] == NULL)
return -ENOENT;
IPSTATS_RTA_PAYLOAD(stats, mrtb[MPLS_STATS_LINK]);
print_nl();
open_json_object("mpls_stats");
print_mpls_link_stats(stdout, &stats, " ");
close_json_object();
return 0;
}
static const struct ipstats_stat_desc ipstats_stat_desc_afstats_mpls = {
.name = "mpls",
.kind = IPSTATS_STAT_DESC_KIND_LEAF,
.pack = &ipstats_stat_desc_pack_afstats,
.show = &ipstats_stat_desc_show_afstats_mpls,
};
static const struct ipstats_stat_desc *ipstats_stat_desc_afstats_subs[] = {
&ipstats_stat_desc_afstats_mpls,
};
static const struct ipstats_stat_desc ipstats_stat_desc_afstats_group = {
.name = "afstats",
.kind = IPSTATS_STAT_DESC_KIND_GROUP,
.subs = ipstats_stat_desc_afstats_subs,
.nsubs = ARRAY_SIZE(ipstats_stat_desc_afstats_subs),
};
static const struct ipstats_stat_desc *ipstats_stat_desc_toplev_subs[] = {
&ipstats_stat_desc_toplev_link,
&ipstats_stat_desc_xstats_group,
&ipstats_stat_desc_xstats_slave_group,
&ipstats_stat_desc_offload_group,
&ipstats_stat_desc_afstats_group,
};
static const struct ipstats_stat_desc ipstats_stat_desc_toplev_group = {
.name = "top-level",
.kind = IPSTATS_STAT_DESC_KIND_GROUP,
.subs = ipstats_stat_desc_toplev_subs,
.nsubs = ARRAY_SIZE(ipstats_stat_desc_toplev_subs),
};
static void ipstats_show_group(const struct ipstats_sel *sel)
{
int i;
for (i = 0; i < IPSTATS_LEVELS_COUNT; i++) {
if (sel->sel[i] == NULL)
break;
print_string(PRINT_JSON, ipstats_levels[i], NULL, sel->sel[i]);
print_string(PRINT_FP, NULL, " %s ", ipstats_levels[i]);
print_string(PRINT_FP, NULL, "%s", sel->sel[i]);
}
}
static int
ipstats_process_ifsm(FILE *fp, struct nlmsghdr *answer,
struct ipstats_stat_enabled *enabled)
{
struct ipstats_stat_show_attrs show_attrs = {};
const char *dev;
int err = 0;
int i;
show_attrs.ifsm = NLMSG_DATA(answer);
show_attrs.len = (answer->nlmsg_len -
NLMSG_LENGTH(sizeof(*show_attrs.ifsm)));
if (show_attrs.len < 0) {
fprintf(stderr, "BUG: wrong nlmsg len %d\n", show_attrs.len);
return -EINVAL;
}
err = ipstats_stat_show_attrs_alloc_tb(&show_attrs, 0);
if (err)
return err;
dev = ll_index_to_name(show_attrs.ifsm->ifindex);
print_headers(fp, "[STATS]");
for (i = 0; i < enabled->nenabled; i++) {
const struct ipstats_stat_desc *desc = enabled->enabled[i].desc;
open_json_object(NULL);
print_int(PRINT_ANY, "ifindex", "%d:",
show_attrs.ifsm->ifindex);
print_color_string(PRINT_ANY, COLOR_IFNAME,
"ifname", " %s:", dev);
ipstats_show_group(&enabled->enabled[i].sel);
err = desc->show(&show_attrs, desc);
if (err != 0)
goto out;
close_json_object();
print_nl();
}
out:
ipstats_stat_show_attrs_free(&show_attrs);
return err;
}
static bool
ipstats_req_should_filter_at(struct ipstats_stat_dump_filters *filters, int at)
{
return filters->mask[at] != 0 &&
filters->mask[at] != (1 << ipstats_stat_ifla_max[at]) - 1;
}
static int
ipstats_req_add_filters(struct ipstats_req *req, void *data)
{
struct ipstats_stat_dump_filters dump_filters = {};
struct ipstats_stat_enabled *enabled = data;
bool get_filters = false;
int i;
for (i = 0; i < enabled->nenabled; i++)
enabled->enabled[i].desc->pack(&dump_filters,
enabled->enabled[i].desc);
for (i = 1; i < ARRAY_SIZE(dump_filters.mask); i++) {
if (ipstats_req_should_filter_at(&dump_filters, i)) {
get_filters = true;
break;
}
}
req->ifsm.filter_mask = dump_filters.mask[0];
if (get_filters) {
struct rtattr *nest;
nest = addattr_nest(&req->nlh, sizeof(*req),
IFLA_STATS_GET_FILTERS | NLA_F_NESTED);
for (i = 1; i < ARRAY_SIZE(dump_filters.mask); i++) {
if (ipstats_req_should_filter_at(&dump_filters, i))
addattr32(&req->nlh, sizeof(*req), i,
dump_filters.mask[i]);
}
addattr_nest_end(&req->nlh, nest);
}
return 0;
}
static int
ipstats_show_one(int ifindex, struct ipstats_stat_enabled *enabled)
{
struct ipstats_req req = {
.nlh.nlmsg_flags = NLM_F_REQUEST,
.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg)),
.nlh.nlmsg_type = RTM_GETSTATS,
.ifsm.family = PF_UNSPEC,
.ifsm.ifindex = ifindex,
};
struct nlmsghdr *answer;
int err = 0;
ipstats_req_add_filters(&req, enabled);
if (rtnl_talk(&rth, &req.nlh, &answer) < 0)
return -2;
err = ipstats_process_ifsm(stdout, answer, enabled);
free(answer);
return err;
}
static int ipstats_dump_one(struct nlmsghdr *n, void *arg)
{
struct ipstats_stat_enabled *enabled = arg;
int rc;
rc = ipstats_process_ifsm(stdout, n, enabled);
if (rc)
return rc;
print_nl();
return 0;
}
static int ipstats_dump(struct ipstats_stat_enabled *enabled)
{
int rc = 0;
if (rtnl_statsdump_req_filter(&rth, PF_UNSPEC, 0,
ipstats_req_add_filters,
enabled) < 0) {
perror("Cannot send dump request");
return -2;
}
if (rtnl_dump_filter(&rth, ipstats_dump_one, enabled) < 0) {
fprintf(stderr, "Dump terminated\n");
rc = -2;
}
fflush(stdout);
return rc;
}
static int
ipstats_show_do(int ifindex, struct ipstats_stat_enabled *enabled)
{
int rc;
new_json_obj(json);
if (ifindex)
rc = ipstats_show_one(ifindex, enabled);
else
rc = ipstats_dump(enabled);
delete_json_obj();
return rc;
}
static int ipstats_add_enabled(struct ipstats_stat_enabled_one ens[],
size_t nens,
struct ipstats_stat_enabled *enabled)
{
struct ipstats_stat_enabled_one *new_en;
new_en = realloc(enabled->enabled,
sizeof(*new_en) * (enabled->nenabled + nens));
if (new_en == NULL)
return -ENOMEM;
enabled->enabled = new_en;
while (nens-- > 0)
enabled->enabled[enabled->nenabled++] = *ens++;
return 0;
}
static void ipstats_select_push(struct ipstats_sel *sel, const char *name)
{
int i;
for (i = 0; i < IPSTATS_LEVELS_COUNT; i++)
if (sel->sel[i] == NULL) {
sel->sel[i] = name;
return;
}
assert(false);
}
static int
ipstats_enable_recursively(const struct ipstats_stat_desc *desc,
struct ipstats_stat_enabled *enabled,
const struct ipstats_sel *sel)
{
bool found = false;
size_t i;
int err;
if (desc->kind == IPSTATS_STAT_DESC_KIND_LEAF) {
struct ipstats_stat_enabled_one en[] = {{
.desc = desc,
.sel = *sel,
}};
return ipstats_add_enabled(en, ARRAY_SIZE(en), enabled);
}
for (i = 0; i < desc->nsubs; i++) {
struct ipstats_sel subsel = *sel;
ipstats_select_push(&subsel, desc->subs[i]->name);
err = ipstats_enable_recursively(desc->subs[i], enabled,
&subsel);
if (err == -ENOENT)
continue;
if (err != 0)
return err;
found = true;
}
return found ? 0 : -ENOENT;
}
static int ipstats_comp_enabled(const void *a, const void *b)
{
const struct ipstats_stat_enabled_one *en_a = a;
const struct ipstats_stat_enabled_one *en_b = b;
if (en_a->desc < en_b->desc)
return -1;
if (en_a->desc > en_b->desc)
return 1;
return 0;
}
static void ipstats_enabled_free(struct ipstats_stat_enabled *enabled)
{
free(enabled->enabled);
}
static const struct ipstats_stat_desc *
ipstats_stat_desc_find(const struct ipstats_stat_desc *desc,
const char *name)
{
size_t i;
assert(desc->kind == IPSTATS_STAT_DESC_KIND_GROUP);
for (i = 0; i < desc->nsubs; i++) {
const struct ipstats_stat_desc *sub = desc->subs[i];
if (strcmp(sub->name, name) == 0)
return sub;
}
return NULL;
}
static const struct ipstats_stat_desc *
ipstats_enable_find_stat_desc(struct ipstats_sel *sel)
{
const struct ipstats_stat_desc *toplev = &ipstats_stat_desc_toplev_group;
const struct ipstats_stat_desc *desc = toplev;
int i;
for (i = 0; i < IPSTATS_LEVELS_COUNT; i++) {
const struct ipstats_stat_desc *next_desc;
if (sel->sel[i] == NULL)
break;
if (desc->kind == IPSTATS_STAT_DESC_KIND_LEAF) {
fprintf(stderr, "Error: %s %s requested inside leaf %s %s\n",
ipstats_levels[i], sel->sel[i],
ipstats_levels[i - 1], desc->name);
return NULL;
}
next_desc = ipstats_stat_desc_find(desc, sel->sel[i]);
if (next_desc == NULL) {
fprintf(stderr, "Error: no %s named %s found inside %s\n",
ipstats_levels[i], sel->sel[i], desc->name);
return NULL;
}
desc = next_desc;
}
return desc;
}
static int ipstats_enable(struct ipstats_sel *sel,
struct ipstats_stat_enabled *enabled)
{
struct ipstats_stat_enabled new_enabled = {};
const struct ipstats_stat_desc *desc;
size_t i, j;
int err = 0;
desc = ipstats_enable_find_stat_desc(sel);
if (desc == NULL)
return -EINVAL;
err = ipstats_enable_recursively(desc, &new_enabled, sel);
if (err != 0)
return err;
err = ipstats_add_enabled(new_enabled.enabled, new_enabled.nenabled,
enabled);
if (err != 0)
goto out;
qsort(enabled->enabled, enabled->nenabled, sizeof(*enabled->enabled),
ipstats_comp_enabled);
for (i = 1, j = 1; i < enabled->nenabled; i++) {
if (enabled->enabled[i].desc != enabled->enabled[j - 1].desc)
enabled->enabled[j++] = enabled->enabled[i];
}
enabled->nenabled = j;
out:
ipstats_enabled_free(&new_enabled);
return err;
}
static int ipstats_enable_check(struct ipstats_sel *sel,
struct ipstats_stat_enabled *enabled)
{
int err;
int i;
err = ipstats_enable(sel, enabled);
if (err == -ENOENT) {
fprintf(stderr, "The request for");
for (i = 0; i < IPSTATS_LEVELS_COUNT; i++)
if (sel->sel[i] != NULL)
fprintf(stderr, " %s %s",
ipstats_levels[i], sel->sel[i]);
else
break;
fprintf(stderr, " did not match any known stats.\n");
}
return err;
}
static int do_help(void)
{
const struct ipstats_stat_desc *toplev = &ipstats_stat_desc_toplev_group;
int i;
fprintf(stderr,
"Usage: ip stats help\n"
" ip stats show [ dev DEV ] [ group GROUP [ subgroup SUBGROUP [ suite SUITE ] ... ] ... ] ...\n"
" ip stats set dev DEV l3_stats { on | off }\n"
);
for (i = 0; i < toplev->nsubs; i++) {
const struct ipstats_stat_desc *desc = toplev->subs[i];
if (i == 0)
fprintf(stderr, "GROUP := { %s", desc->name);
else
fprintf(stderr, " | %s", desc->name);
}
if (i > 0)
fprintf(stderr, " }\n");
for (i = 0; i < toplev->nsubs; i++) {
const struct ipstats_stat_desc *desc = toplev->subs[i];
bool opened = false;
size_t j;
if (desc->kind != IPSTATS_STAT_DESC_KIND_GROUP)
continue;
for (j = 0; j < desc->nsubs; j++) {
size_t k;
if (j == 0)
fprintf(stderr, "%s SUBGROUP := {", desc->name);
else
fprintf(stderr, " |");
fprintf(stderr, " %s", desc->subs[j]->name);
opened = true;
if (desc->subs[j]->kind != IPSTATS_STAT_DESC_KIND_GROUP)
continue;
for (k = 0; k < desc->subs[j]->nsubs; k++)
fprintf(stderr, " [ suite %s ]",
desc->subs[j]->subs[k]->name);
}
if (opened)
fprintf(stderr, " }\n");
}
return 0;
}
static int ipstats_select(struct ipstats_sel *old_sel,
const char *new_sel, int level,
struct ipstats_stat_enabled *enabled)
{
int err;
int i;
for (i = 0; i < level; i++) {
if (old_sel->sel[i] == NULL) {
fprintf(stderr, "Error: %s %s requested without selecting a %s first\n",
ipstats_levels[level], new_sel,
ipstats_levels[i]);
return -EINVAL;
}
}
for (i = level; i < IPSTATS_LEVELS_COUNT; i++) {
if (old_sel->sel[i] != NULL) {
err = ipstats_enable_check(old_sel, enabled);
if (err)
return err;
break;
}
}
old_sel->sel[level] = new_sel;
for (i = level + 1; i < IPSTATS_LEVELS_COUNT; i++)
old_sel->sel[i] = NULL;
return 0;
}
static int ipstats_show(int argc, char **argv)
{
struct ipstats_stat_enabled enabled = {};
struct ipstats_sel sel = {};
const char *dev = NULL;
int ifindex;
int err;
int i;
while (argc > 0) {
if (strcmp(*argv, "dev") == 0) {
NEXT_ARG();
if (dev != NULL)
duparg2("dev", *argv);
if (check_ifname(*argv))
invarg("\"dev\" not a valid ifname", *argv);
dev = *argv;
} else if (strcmp(*argv, "help") == 0) {
do_help();
return 0;
} else {
bool found_level = false;
for (i = 0; i < ARRAY_SIZE(ipstats_levels); i++) {
if (strcmp(*argv, ipstats_levels[i]) == 0) {
NEXT_ARG();
err = ipstats_select(&sel, *argv, i,
&enabled);
if (err)
goto err;
found_level = true;
}
}
if (!found_level) {
fprintf(stderr, "What is \"%s\"?\n", *argv);
do_help();
err = -EINVAL;
goto err;
}
}
NEXT_ARG_FWD();
}
/* Push whatever was given. */
err = ipstats_enable_check(&sel, &enabled);
if (err)
goto err;
if (dev) {
ifindex = ll_name_to_index(dev);
if (!ifindex) {
err = nodev(dev);
goto err;
}
} else {
ifindex = 0;
}
err = ipstats_show_do(ifindex, &enabled);
err:
ipstats_enabled_free(&enabled);
return err;
}
static int ipstats_set_do(int ifindex, int at, bool enable)
{
struct ipstats_req req = {
.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg)),
.nlh.nlmsg_flags = NLM_F_REQUEST,
.nlh.nlmsg_type = RTM_SETSTATS,
.ifsm.family = PF_UNSPEC,
.ifsm.ifindex = ifindex,
};
addattr8(&req.nlh, sizeof(req), at, enable);
if (rtnl_talk(&rth, &req.nlh, NULL) < 0)
return -2;
return 0;
}
static int ipstats_set(int argc, char **argv)
{
const char *dev = NULL;
bool enable = false;
int ifindex;
int at = 0;
while (argc > 0) {
if (strcmp(*argv, "dev") == 0) {
NEXT_ARG();
if (dev)
duparg2("dev", *argv);
if (check_ifname(*argv))
invarg("\"dev\" not a valid ifname", *argv);
dev = *argv;
} else if (strcmp(*argv, "l3_stats") == 0) {
int err;
NEXT_ARG();
if (at) {
fprintf(stderr, "A statistics suite to toggle was already given.\n");
return -EINVAL;
}
at = IFLA_STATS_SET_OFFLOAD_XSTATS_L3_STATS;
enable = parse_on_off("l3_stats", *argv, &err);
if (err)
return err;
} else if (strcmp(*argv, "help") == 0) {
do_help();
return 0;
} else {
fprintf(stderr, "What is \"%s\"?\n", *argv);
do_help();
return -EINVAL;
}
NEXT_ARG_FWD();
}
if (!dev) {
fprintf(stderr, "Not enough information: \"dev\" argument is required.\n");
exit(-1);
}
if (!at) {
fprintf(stderr, "Not enough information: stat type to toggle is required.\n");
exit(-1);
}
ifindex = ll_name_to_index(dev);
if (!ifindex)
return nodev(dev);
return ipstats_set_do(ifindex, at, enable);
}
int do_ipstats(int argc, char **argv)
{
int rc;
if (argc == 0) {
rc = ipstats_show(0, NULL);
} else if (strcmp(*argv, "help") == 0) {
do_help();
rc = 0;
} else if (strcmp(*argv, "show") == 0) {
/* Invoking "stats show" implies one -s. Passing -d adds one
* more -s.
*/
show_stats += show_details + 1;
rc = ipstats_show(argc-1, argv+1);
} else if (strcmp(*argv, "set") == 0) {
rc = ipstats_set(argc-1, argv+1);
} else {
fprintf(stderr, "Command \"%s\" is unknown, try \"ip stats help\".\n",
*argv);
rc = -1;
}
return rc;
}
int ipstats_print(struct nlmsghdr *n, void *arg)
{
struct ipstats_stat_enabled_one one = {
.desc = &ipstats_stat_desc_offload_hw_s_info,
};
struct ipstats_stat_enabled enabled = {
.enabled = &one,
.nenabled = 1,
};
FILE *fp = arg;
int rc;
rc = ipstats_process_ifsm(fp, n, &enabled);
if (rc)
return rc;
fflush(fp);
return 0;
}