// SPDX-License-Identifier: GPL-2.0+ #include #include #include #include #include #include #include #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) return -ENOMEM; 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(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 != 0) { fprintf(stderr, "Error parsing netlink answer: %s\n", strerror(err)); return err; } dev = ll_index_to_name(show_attrs.ifsm->ifindex); 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(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(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(n, &enabled); if (rc) return rc; fflush(fp); return 0; }