mirror of
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git
synced 2024-11-14 21:44:41 +08:00
554ea3649d
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>
1364 lines
32 KiB
C
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;
|
|
}
|