diff options
41 files changed, 2984 insertions, 275 deletions
diff --git a/bridge/Makefile b/bridge/Makefile index c6b7d08d..01f8a455 100644 --- a/bridge/Makefile +++ b/bridge/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -BROBJ = bridge.o fdb.o monitor.o link.o mdb.o vlan.o +BROBJ = bridge.o fdb.o monitor.o link.o mdb.o vlan.o vni.o include ../config.mk diff --git a/bridge/br_common.h b/bridge/br_common.h index 610e83f6..841f0594 100644 --- a/bridge/br_common.h +++ b/bridge/br_common.h @@ -14,6 +14,7 @@ void print_stp_state(__u8 state); int parse_stp_state(const char *arg); int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor, bool global_only); +int print_vnifilter_rtm(struct nlmsghdr *n, void *arg, bool monitor); void br_print_router_port_stats(struct rtattr *pattr); int do_fdb(int argc, char **argv); @@ -21,6 +22,7 @@ int do_mdb(int argc, char **argv); int do_monitor(int argc, char **argv); int do_vlan(int argc, char **argv); int do_link(int argc, char **argv); +int do_vni(int argc, char **argv); extern int preferred_family; extern int show_stats; diff --git a/bridge/bridge.c b/bridge/bridge.c index f3a4f08f..704be50c 100644 --- a/bridge/bridge.c +++ b/bridge/bridge.c @@ -58,6 +58,7 @@ static const struct cmd { { "fdb", do_fdb }, { "mdb", do_mdb }, { "vlan", do_vlan }, + { "vni", do_vni }, { "monitor", do_monitor }, { "help", do_help }, { 0 } diff --git a/bridge/monitor.c b/bridge/monitor.c index 845e221a..f17c1906 100644 --- a/bridge/monitor.c +++ b/bridge/monitor.c @@ -31,10 +31,20 @@ static int prefix_banner; static void usage(void) { - fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | vlan | all]\n"); + fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | vlan | vni | all]\n"); exit(-1); } +static int print_tunnel_rtm(struct nlmsghdr *n, void *arg, bool monitor) +{ + struct tunnel_msg *tmsg = NLMSG_DATA(n); + + if (tmsg->family == PF_BRIDGE) + return print_vnifilter_rtm(n, arg, monitor); + + return 0; +} + static int accept_msg(struct rtnl_ctrl_data *ctrl, struct nlmsghdr *n, void *arg) { @@ -73,6 +83,12 @@ static int accept_msg(struct rtnl_ctrl_data *ctrl, fprintf(fp, "[VLAN]"); return print_vlan_rtm(n, arg, true, false); + case RTM_NEWTUNNEL: + case RTM_DELTUNNEL: + if (prefix_banner) + fprintf(fp, "[TUNNEL]"); + return print_tunnel_rtm(n, arg, true); + default: return 0; } @@ -86,6 +102,7 @@ int do_monitor(int argc, char **argv) int lneigh = 0; int lmdb = 0; int lvlan = 0; + int lvni = 0; rtnl_close(&rth); @@ -105,9 +122,13 @@ int do_monitor(int argc, char **argv) } else if (matches(*argv, "vlan") == 0) { lvlan = 1; groups = 0; + } else if (strcmp(*argv, "vni") == 0) { + lvni = 1; + groups = 0; } else if (strcmp(*argv, "all") == 0) { groups = ~RTMGRP_TC; lvlan = 1; + lvni = 1; prefix_banner = 1; } else if (matches(*argv, "help") == 0) { usage(); @@ -151,6 +172,11 @@ int do_monitor(int argc, char **argv) exit(1); } + if (lvni && rtnl_add_nl_group(&rth, RTNLGRP_TUNNEL) < 0) { + fprintf(stderr, "Failed to add bridge vni group to list\n"); + exit(1); + } + ll_init_map(&rth); if (rtnl_listen(&rth, accept_msg, stdout) < 0) diff --git a/bridge/vlan.c b/bridge/vlan.c index 8300f353..390a2037 100644 --- a/bridge/vlan.c +++ b/bridge/vlan.c @@ -1179,7 +1179,8 @@ static int vlan_show(int argc, char **argv, int subject) __u32 filt_mask; filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS); - if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) { + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { perror("Cannot send dump request"); exit(1); } @@ -1194,7 +1195,8 @@ static int vlan_show(int argc, char **argv, int subject) } filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS_SLAVE); - if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) { + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { perror("Cannot send slave dump request"); exit(1); } diff --git a/bridge/vni.c b/bridge/vni.c new file mode 100644 index 00000000..a0c2792c --- /dev/null +++ b/bridge/vni.c @@ -0,0 +1,439 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Command to manage vnifiltering on a vxlan device + * + * Authors: Roopa Prabhu <roopa@nvidia.com> + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <linux/if_link.h> +#include <linux/if_bridge.h> +#include <linux/if_ether.h> + +#include "json_print.h" +#include "libnetlink.h" +#include "br_common.h" +#include "utils.h" + +static unsigned int filter_index; + +#define VXLAN_ID_LEN 15 + +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +static void usage(void) +{ + fprintf(stderr, + "Usage: bridge vni { add | del } vni VNI\n" + " [ { group | remote } IP_ADDRESS ]\n" + " [ dev DEV ]\n" + " bridge vni { show }\n" + "\n" + "Where: VNI := 0-16777215\n" + ); + exit(-1); +} + +static int parse_vni_filter(const char *argv, struct nlmsghdr *n, int reqsize, + inet_prefix *group) +{ + char *vnilist = strdupa(argv); + char *vni = strtok(vnilist, ","); + int group_type = AF_UNSPEC; + struct rtattr *nlvlist_e; + char *v; + int i; + + if (group && is_addrtype_inet(group)) + group_type = (group->family == AF_INET) ? VXLAN_VNIFILTER_ENTRY_GROUP : + VXLAN_VNIFILTER_ENTRY_GROUP6; + + for (i = 0; vni; i++) { + __u32 vni_start = 0, vni_end = 0; + + v = strchr(vni, '-'); + if (v) { + *v = '\0'; + v++; + vni_start = atoi(vni); + vni_end = atoi(v); + } else { + vni_start = atoi(vni); + } + nlvlist_e = addattr_nest(n, reqsize, VXLAN_VNIFILTER_ENTRY | + NLA_F_NESTED); + addattr32(n, 1024, VXLAN_VNIFILTER_ENTRY_START, vni_start); + if (vni_end) + addattr32(n, 1024, VXLAN_VNIFILTER_ENTRY_END, vni_end); + if (group) + addattr_l(n, 1024, group_type, group->data, group->bytelen); + addattr_nest_end(n, nlvlist_e); + vni = strtok(NULL, ","); + } + + return 0; +} + +static int vni_modify(int cmd, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct tunnel_msg tmsg; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tunnel_msg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = cmd, + .tmsg.family = PF_BRIDGE, + }; + bool group_present = false; + inet_prefix daddr; + char *vni = NULL; + char *d = NULL; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "vni") == 0) { + NEXT_ARG(); + if (vni) + invarg("duplicate vni", *argv); + vni = *argv; + } else if (strcmp(*argv, "group") == 0) { + if (group_present) + invarg("duplicate group", *argv); + if (is_addrtype_inet_not_multi(&daddr)) { + fprintf(stderr, "vxlan: both group and remote"); + fprintf(stderr, " cannot be specified\n"); + return -1; + } + NEXT_ARG(); + get_addr(&daddr, *argv, AF_UNSPEC); + if (!is_addrtype_inet_multi(&daddr)) + invarg("invalid group address", *argv); + group_present = true; + } else if (strcmp(*argv, "remote") == 0) { + if (group_present) + invarg("duplicate group", *argv); + NEXT_ARG(); + get_addr(&daddr, *argv, AF_UNSPEC); + group_present = true; + } else { + if (strcmp(*argv, "help") == 0) + usage(); + } + argc--; argv++; + } + + if (d == NULL || vni == NULL) { + fprintf(stderr, "Device and VNI ID are required arguments.\n"); + return -1; + } + + if (!vni && group_present) { + fprintf(stderr, "Group can only be specified with a vni\n"); + return -1; + } + + if (vni) + parse_vni_filter(vni, &req.n, sizeof(req), + (group_present ? &daddr : NULL)); + + req.tmsg.ifindex = ll_name_to_index(d); + if (req.tmsg.ifindex == 0) { + fprintf(stderr, "Cannot find vxlan device \"%s\"\n", d); + return -1; + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +static void open_vni_port(int ifi_index, const char *fmt) +{ + open_json_object(NULL); + print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", + "%-" __stringify(IFNAMSIZ) "s ", + ll_index_to_name(ifi_index)); + open_json_array(PRINT_JSON, "vnis"); +} + +static void close_vni_port(void) +{ + close_json_array(PRINT_JSON, NULL); + close_json_object(); +} + +static void print_range(const char *name, __u32 start, __u32 id) +{ + char end[64]; + + snprintf(end, sizeof(end), "%sEnd", name); + + print_uint(PRINT_ANY, name, " %u", start); + if (start != id) + print_uint(PRINT_ANY, end, "-%-14u ", id); + +} + +static void print_vnifilter_entry_stats(struct rtattr *stats_attr) +{ + struct rtattr *stb[VNIFILTER_ENTRY_STATS_MAX+1]; + __u64 stat; + + open_json_object("stats"); + parse_rtattr_flags(stb, VNIFILTER_ENTRY_STATS_MAX, RTA_DATA(stats_attr), + RTA_PAYLOAD(stats_attr), NLA_F_NESTED); + + print_nl(); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + print_string(PRINT_FP, NULL, "RX: ", ""); + + if (stb[VNIFILTER_ENTRY_STATS_RX_BYTES]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_BYTES]); + print_lluint(PRINT_ANY, "rx_bytes", "bytes %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_RX_PKTS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_PKTS]); + print_lluint(PRINT_ANY, "rx_pkts", "pkts %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_RX_DROPS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_DROPS]); + print_lluint(PRINT_ANY, "rx_drops", "drops %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_RX_ERRORS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_ERRORS]); + print_lluint(PRINT_ANY, "rx_errors", "errors %llu ", stat); + } + + print_nl(); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + print_string(PRINT_FP, NULL, "TX: ", ""); + + if (stb[VNIFILTER_ENTRY_STATS_TX_BYTES]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_BYTES]); + print_lluint(PRINT_ANY, "tx_bytes", "bytes %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_TX_PKTS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_PKTS]); + print_lluint(PRINT_ANY, "tx_pkts", "pkts %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_TX_DROPS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_DROPS]); + print_lluint(PRINT_ANY, "tx_drops", "drops %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_TX_ERRORS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_ERRORS]); + print_lluint(PRINT_ANY, "tx_errors", "errors %llu ", stat); + } + close_json_object(); +} + +static void print_vni(struct rtattr *t, int ifindex) +{ + struct rtattr *ttb[VXLAN_VNIFILTER_ENTRY_MAX+1]; + __u32 vni_start = 0; + __u32 vni_end = 0; + + parse_rtattr_flags(ttb, VXLAN_VNIFILTER_ENTRY_MAX, RTA_DATA(t), + RTA_PAYLOAD(t), NLA_F_NESTED); + + if (ttb[VXLAN_VNIFILTER_ENTRY_START]) + vni_start = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_START]); + + if (ttb[VXLAN_VNIFILTER_ENTRY_END]) + vni_end = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_END]); + + if (vni_end) + print_range("vni", vni_start, vni_end); + else + print_uint(PRINT_ANY, "vni", " %-14u", vni_start); + + if (ttb[VXLAN_VNIFILTER_ENTRY_GROUP]) { + __be32 addr = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_GROUP]); + + if (addr) { + if (IN_MULTICAST(ntohl(addr))) + print_string(PRINT_ANY, + "group", + " %s", + format_host(AF_INET, 4, &addr)); + else + print_string(PRINT_ANY, + "remote", + " %s", + format_host(AF_INET, 4, &addr)); + } + } else if (ttb[VXLAN_VNIFILTER_ENTRY_GROUP6]) { + struct in6_addr addr; + + memcpy(&addr, RTA_DATA(ttb[VXLAN_VNIFILTER_ENTRY_GROUP6]), sizeof(struct in6_addr)); + if (!IN6_IS_ADDR_UNSPECIFIED(&addr)) { + if (IN6_IS_ADDR_MULTICAST(&addr)) + print_string(PRINT_ANY, + "group", + " %s", + format_host(AF_INET6, + sizeof(struct in6_addr), + &addr)); + else + print_string(PRINT_ANY, + "remote", + " %s", + format_host(AF_INET6, + sizeof(struct in6_addr), + &addr)); + } + } + + if (ttb[VXLAN_VNIFILTER_ENTRY_STATS]) + print_vnifilter_entry_stats(ttb[VXLAN_VNIFILTER_ENTRY_STATS]); + + close_json_object(); + print_string(PRINT_FP, NULL, "%s", _SL_); +} + +int print_vnifilter_rtm(struct nlmsghdr *n, void *arg, bool monitor) +{ + struct tunnel_msg *tmsg = NLMSG_DATA(n); + int len = n->nlmsg_len; + bool first = true; + struct rtattr *t; + int rem; + + if (n->nlmsg_type != RTM_NEWTUNNEL && + n->nlmsg_type != RTM_DELTUNNEL && + n->nlmsg_type != RTM_GETTUNNEL) { + fprintf(stderr, "Unknown vni tunnel rtm msg: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return 0; + } + + len -= NLMSG_LENGTH(sizeof(*tmsg)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + if (tmsg->family != AF_BRIDGE) + return 0; + + if (filter_index && filter_index != tmsg->ifindex) + return 0; + + if (n->nlmsg_type == RTM_DELTUNNEL) + print_bool(PRINT_ANY, "deleted", "Deleted ", true); + + rem = len; + for (t = TUNNEL_RTA(tmsg); RTA_OK(t, rem); t = RTA_NEXT(t, rem)) { + unsigned short rta_type = t->rta_type & NLA_TYPE_MASK; + + if (rta_type != VXLAN_VNIFILTER_ENTRY) + continue; + if (first) { + open_vni_port(tmsg->ifindex, "%s"); + open_json_object(NULL); + first = false; + } else { + open_json_object(NULL); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + } + + print_vni(t, tmsg->ifindex); + } + close_vni_port(); + + print_string(PRINT_FP, NULL, "%s", _SL_); + + fflush(stdout); + return 0; +} + +static int print_vnifilter_rtm_filter(struct nlmsghdr *n, void *arg) +{ + return print_vnifilter_rtm(n, arg, false); +} + +static int vni_show(int argc, char **argv) +{ + char *filter_dev = NULL; + __u8 flags = 0; + int ret = 0; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (filter_dev) + duparg("dev", *argv); + filter_dev = *argv; + } + argc--; argv++; + } + + if (filter_dev) { + filter_index = ll_name_to_index(filter_dev); + if (!filter_index) + return nodev(filter_dev); + } + + new_json_obj(json); + + if (show_stats) + flags = TUNNEL_MSG_FLAG_STATS; + + if (rtnl_tunneldump_req(&rth, PF_BRIDGE, filter_index, flags) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) { + printf("%-" __stringify(IFNAMSIZ) "s %-" + __stringify(VXLAN_ID_LEN) "s %-" + __stringify(15) "s", + "dev", "vni", "group/remote"); + printf("\n"); + } + + ret = rtnl_dump_filter(&rth, print_vnifilter_rtm_filter, NULL); + if (ret < 0) { + fprintf(stderr, "Dump ternminated\n"); + exit(1); + } + + delete_json_obj(); + fflush(stdout); + return 0; +} + +int do_vni(int argc, char **argv) +{ + ll_init_map(&rth); + + if (argc > 0) { + if (strcmp(*argv, "add") == 0) + return vni_modify(RTM_NEWTUNNEL, argc-1, argv+1); + if (strcmp(*argv, "delete") == 0) + return vni_modify(RTM_DELTUNNEL, argc-1, argv+1); + if (strcmp(*argv, "show") == 0 || + strcmp(*argv, "lst") == 0 || + strcmp(*argv, "list") == 0) + return vni_show(argc-1, argv+1); + if (strcmp(*argv, "help") == 0) + usage(); + } else { + return vni_show(0, NULL); + } + + fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vni help\".\n", *argv); + exit(-1); +} diff --git a/devlink/devlink.c b/devlink/devlink.c index aab739f7..ddf430bb 100644 --- a/devlink/devlink.c +++ b/devlink/devlink.c @@ -367,6 +367,7 @@ struct dl { bool pretty_output; bool verbose; bool stats; + bool hex; struct { bool present; char *bus_name; @@ -8044,6 +8045,8 @@ static int cmd_health_dump_clear(struct dl *dl) static int fmsg_value_show(struct dl *dl, int type, struct nlattr *nl_data) { + const char *num_fmt = dl->hex ? "%#x" : "%u"; + const char *num64_fmt = dl->hex ? "%#"PRIx64 : "%"PRIu64; uint8_t *data; uint32_t len; @@ -8053,16 +8056,16 @@ static int fmsg_value_show(struct dl *dl, int type, struct nlattr *nl_data) print_bool(PRINT_ANY, NULL, "%s", mnl_attr_get_u8(nl_data)); break; case MNL_TYPE_U8: - print_uint(PRINT_ANY, NULL, "%u", mnl_attr_get_u8(nl_data)); + print_uint(PRINT_ANY, NULL, num_fmt, mnl_attr_get_u8(nl_data)); break; case MNL_TYPE_U16: - print_uint(PRINT_ANY, NULL, "%u", mnl_attr_get_u16(nl_data)); + print_uint(PRINT_ANY, NULL, num_fmt, mnl_attr_get_u16(nl_data)); break; case MNL_TYPE_U32: - print_uint(PRINT_ANY, NULL, "%u", mnl_attr_get_u32(nl_data)); + print_uint(PRINT_ANY, NULL, num_fmt, mnl_attr_get_u32(nl_data)); break; case MNL_TYPE_U64: - print_u64(PRINT_ANY, NULL, "%"PRIu64, mnl_attr_get_u64(nl_data)); + print_u64(PRINT_ANY, NULL, num64_fmt, mnl_attr_get_u64(nl_data)); break; case MNL_TYPE_NUL_STRING: print_string(PRINT_ANY, NULL, "%s", mnl_attr_get_str(nl_data)); @@ -8939,7 +8942,7 @@ static void help(void) pr_err("Usage: devlink [ OPTIONS ] OBJECT { COMMAND | help }\n" " devlink [ -f[orce] ] -b[atch] filename -N[etns] netnsname\n" "where OBJECT := { dev | port | sb | monitor | dpipe | resource | region | health | trap }\n" - " OPTIONS := { -V[ersion] | -n[o-nice-names] | -j[son] | -p[retty] | -v[erbose] -s[tatistics] }\n"); + " OPTIONS := { -V[ersion] | -n[o-nice-names] | -j[son] | -p[retty] | -v[erbose] -s[tatistics] -[he]x }\n"); } static int dl_cmd(struct dl *dl, int argc, char **argv) @@ -9053,6 +9056,7 @@ int main(int argc, char **argv) { "statistics", no_argument, NULL, 's' }, { "Netns", required_argument, NULL, 'N' }, { "iec", no_argument, NULL, 'i' }, + { "hex", no_argument, NULL, 'x' }, { NULL, 0, NULL, 0 } }; const char *batch_file = NULL; @@ -9068,7 +9072,7 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - while ((opt = getopt_long(argc, argv, "Vfb:njpvsN:i", + while ((opt = getopt_long(argc, argv, "Vfb:njpvsN:ix", long_options, NULL)) >= 0) { switch (opt) { @@ -9106,6 +9110,9 @@ int main(int argc, char **argv) case 'i': use_iec = true; break; + case 'x': + dl->hex = true; + break; default: pr_err("Unknown option.\n"); help(); diff --git a/include/libnetlink.h b/include/libnetlink.h index 9e4cc101..a7b0f352 100644 --- a/include/libnetlink.h +++ b/include/libnetlink.h @@ -37,6 +37,12 @@ struct nlmsg_chain { struct nlmsg_list *tail; }; +struct ipstats_req { + struct nlmsghdr nlh; + struct if_stats_msg ifsm; + char buf[128]; +}; + extern int rcvbuf; int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions) @@ -88,7 +94,10 @@ int rtnl_fdb_linkdump_req_filter_fn(struct rtnl_handle *rth, int rtnl_nsiddump_req_filter_fn(struct rtnl_handle *rth, int family, req_filter_fn_t filter_fn) __attribute__((warn_unused_result)); -int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask) +int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask, + int (*filter_fn)(struct ipstats_req *req, + void *data), + void *filter_data) __attribute__((warn_unused_result)); int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) @@ -103,6 +112,10 @@ int rtnl_nexthop_bucket_dump_req(struct rtnl_handle *rth, int family, req_filter_fn_t filter_fn) __attribute__((warn_unused_result)); +int rtnl_tunneldump_req(struct rtnl_handle *rth, int family, int ifindex, + __u8 flags) + __attribute__((warn_unused_result)); + struct rtnl_ctrl_data { int nsid; }; @@ -322,6 +335,11 @@ int rtnl_from_file(FILE *, rtnl_listen_filter_t handler, ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct br_vlan_msg)))) #endif +#ifndef TUNNEL_RTA +#define TUNNEL_RTA(r) \ + ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct tunnel_msg)))) +#endif + /* User defined nlmsg_type which is used mostly for logging netlink * messages from dump file */ #define NLMSG_TSTAMP 15 diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 5889a4d3..2892794f 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -1013,6 +1013,7 @@ enum bpf_link_type { BPF_LINK_TYPE_XDP = 6, BPF_LINK_TYPE_PERF_EVENT = 7, BPF_LINK_TYPE_KPROBE_MULTI = 8, + BPF_LINK_TYPE_STRUCT_OPS = 9, MAX_BPF_LINK_TYPE, }; @@ -1489,6 +1490,15 @@ union bpf_attr { __aligned_u64 addrs; __aligned_u64 cookies; } kprobe_multi; + struct { + /* this is overlaid with the target_btf_id above. */ + __u32 target_btf_id; + /* black box user-provided value passed through + * to BPF program at the execution time and + * accessible through bpf_get_attach_cookie() BPF helper + */ + __u64 cookie; + } tracing; }; } link_create; @@ -5143,6 +5153,102 @@ union bpf_attr { * The **hash_algo** is returned on success, * **-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if * invalid arguments are passed. + * + * void *bpf_kptr_xchg(void *map_value, void *ptr) + * Description + * Exchange kptr at pointer *map_value* with *ptr*, and return the + * old value. *ptr* can be NULL, otherwise it must be a referenced + * pointer which will be released when this helper is called. + * Return + * The old value of kptr (which can be NULL). The returned pointer + * if not NULL, is a reference which must be released using its + * corresponding release function, or moved into a BPF map before + * program exit. + * + * void *bpf_map_lookup_percpu_elem(struct bpf_map *map, const void *key, u32 cpu) + * Description + * Perform a lookup in *percpu map* for an entry associated to + * *key* on *cpu*. + * Return + * Map value associated to *key* on *cpu*, or **NULL** if no entry + * was found or *cpu* is invalid. + * + * struct mptcp_sock *bpf_skc_to_mptcp_sock(void *sk) + * Description + * Dynamically cast a *sk* pointer to a *mptcp_sock* pointer. + * Return + * *sk* if casting is valid, or **NULL** otherwise. + * + * long bpf_dynptr_from_mem(void *data, u32 size, u64 flags, struct bpf_dynptr *ptr) + * Description + * Get a dynptr to local memory *data*. + * + * *data* must be a ptr to a map value. + * The maximum *size* supported is DYNPTR_MAX_SIZE. + * *flags* is currently unused. + * Return + * 0 on success, -E2BIG if the size exceeds DYNPTR_MAX_SIZE, + * -EINVAL if flags is not 0. + * + * long bpf_ringbuf_reserve_dynptr(void *ringbuf, u32 size, u64 flags, struct bpf_dynptr *ptr) + * Description + * Reserve *size* bytes of payload in a ring buffer *ringbuf* + * through the dynptr interface. *flags* must be 0. + * + * Please note that a corresponding bpf_ringbuf_submit_dynptr or + * bpf_ringbuf_discard_dynptr must be called on *ptr*, even if the + * reservation fails. This is enforced by the verifier. + * Return + * 0 on success, or a negative error in case of failure. + * + * void bpf_ringbuf_submit_dynptr(struct bpf_dynptr *ptr, u64 flags) + * Description + * Submit reserved ring buffer sample, pointed to by *data*, + * through the dynptr interface. This is a no-op if the dynptr is + * invalid/null. + * + * For more information on *flags*, please see + * 'bpf_ringbuf_submit'. + * Return + * Nothing. Always succeeds. + * + * void bpf_ringbuf_discard_dynptr(struct bpf_dynptr *ptr, u64 flags) + * Description + * Discard reserved ring buffer sample through the dynptr + * interface. This is a no-op if the dynptr is invalid/null. + * + * For more information on *flags*, please see + * 'bpf_ringbuf_discard'. + * Return + * Nothing. Always succeeds. + * + * long bpf_dynptr_read(void *dst, u32 len, struct bpf_dynptr *src, u32 offset) + * Description + * Read *len* bytes from *src* into *dst*, starting from *offset* + * into *src*. + * Return + * 0 on success, -E2BIG if *offset* + *len* exceeds the length + * of *src*'s data, -EINVAL if *src* is an invalid dynptr. + * + * long bpf_dynptr_write(struct bpf_dynptr *dst, u32 offset, void *src, u32 len) + * Description + * Write *len* bytes from *src* into *dst*, starting from *offset* + * into *dst*. + * Return + * 0 on success, -E2BIG if *offset* + *len* exceeds the length + * of *dst*'s data, -EINVAL if *dst* is an invalid dynptr or if *dst* + * is a read-only dynptr. + * + * void *bpf_dynptr_data(struct bpf_dynptr *ptr, u32 offset, u32 len) + * Description + * Get a pointer to the underlying dynptr data. + * + * *len* must be a statically known value. The returned data slice + * is invalidated whenever the dynptr is invalidated. + * Return + * Pointer to the underlying dynptr data, NULL if the dynptr is + * read-only, if the dynptr is invalid, or if the offset and length + * is out of bounds. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5339,6 +5445,16 @@ union bpf_attr { FN(copy_from_user_task), \ FN(skb_set_tstamp), \ FN(ima_file_hash), \ + FN(kptr_xchg), \ + FN(map_lookup_percpu_elem), \ + FN(skc_to_mptcp_sock), \ + FN(dynptr_from_mem), \ + FN(ringbuf_reserve_dynptr), \ + FN(ringbuf_submit_dynptr), \ + FN(ringbuf_discard_dynptr), \ + FN(dynptr_read), \ + FN(dynptr_write), \ + FN(dynptr_data), \ /* */ /* integer value in 'imm' field of BPF_CALL instruction selects which helper @@ -5592,6 +5708,10 @@ struct bpf_tunnel_key { __u8 tunnel_ttl; __u16 tunnel_ext; /* Padding, future use. */ __u32 tunnel_label; + union { + __u32 local_ipv4; + __u32 local_ipv6[4]; + }; }; /* user accessible mirror of in-kernel xfrm_state. @@ -6486,6 +6606,11 @@ struct bpf_timer { __u64 :64; } __attribute__((aligned(8))); +struct bpf_dynptr { + __u64 :64; + __u64 :64; +} __attribute__((aligned(8))); + struct bpf_sysctl { __u32 write; /* Sysctl is being read (= 0) or written (= 1). * Allows 1,2,4-byte read, but no write. diff --git a/include/uapi/linux/btf.h b/include/uapi/linux/btf.h index 92c41b15..aa0f4a3e 100644 --- a/include/uapi/linux/btf.h +++ b/include/uapi/linux/btf.h @@ -33,8 +33,8 @@ struct btf_type { /* "info" bits arrangement * bits 0-15: vlen (e.g. # of struct's members) * bits 16-23: unused - * bits 24-27: kind (e.g. int, ptr, array...etc) - * bits 28-30: unused + * bits 24-28: kind (e.g. int, ptr, array...etc) + * bits 29-30: unused * bit 31: kind_flag, currently used by * struct, union and fwd */ diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index 210d4fbf..da0f1ba8 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -131,6 +131,11 @@ enum devlink_command { DEVLINK_CMD_RATE_NEW, DEVLINK_CMD_RATE_DEL, + DEVLINK_CMD_LINECARD_GET, /* can dump */ + DEVLINK_CMD_LINECARD_SET, + DEVLINK_CMD_LINECARD_NEW, + DEVLINK_CMD_LINECARD_DEL, + /* add new commands above here */ __DEVLINK_CMD_MAX, DEVLINK_CMD_MAX = __DEVLINK_CMD_MAX - 1 @@ -338,6 +343,19 @@ enum devlink_reload_limit { #define DEVLINK_RELOAD_LIMITS_VALID_MASK (_BITUL(__DEVLINK_RELOAD_LIMIT_MAX) - 1) +enum devlink_linecard_state { + DEVLINK_LINECARD_STATE_UNSPEC, + DEVLINK_LINECARD_STATE_UNPROVISIONED, + DEVLINK_LINECARD_STATE_UNPROVISIONING, + DEVLINK_LINECARD_STATE_PROVISIONING, + DEVLINK_LINECARD_STATE_PROVISIONING_FAILED, + DEVLINK_LINECARD_STATE_PROVISIONED, + DEVLINK_LINECARD_STATE_ACTIVE, + + __DEVLINK_LINECARD_STATE_MAX, + DEVLINK_LINECARD_STATE_MAX = __DEVLINK_LINECARD_STATE_MAX - 1 +}; + enum devlink_attr { /* don't change the order or add anything between, this is ABI! */ DEVLINK_ATTR_UNSPEC, @@ -553,6 +571,11 @@ enum devlink_attr { DEVLINK_ATTR_REGION_MAX_SNAPSHOTS, /* u32 */ + DEVLINK_ATTR_LINECARD_INDEX, /* u32 */ + DEVLINK_ATTR_LINECARD_STATE, /* u8 */ + DEVLINK_ATTR_LINECARD_TYPE, /* string */ + DEVLINK_ATTR_LINECARD_SUPPORTED_TYPES, /* nested */ + /* add new attributes above here, update the policy in devlink.c */ __DEVLINK_ATTR_MAX, diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index 22e21e57..da99b643 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -211,6 +211,9 @@ struct rtnl_link_stats { * @rx_nohandler: Number of packets received on the interface * but dropped by the networking stack because the device is * not designated to receive packets (e.g. backup link in a bond). + * + * @rx_otherhost_dropped: Number of packets dropped due to mismatch + * in destination MAC address. */ struct rtnl_link_stats64 { __u64 rx_packets; @@ -243,6 +246,8 @@ struct rtnl_link_stats64 { __u64 rx_compressed; __u64 tx_compressed; __u64 rx_nohandler; + + __u64 rx_otherhost_dropped; }; /* Subset of link stats useful for in-HW collection. Meaning of the fields is as @@ -363,6 +368,8 @@ enum { IFLA_PARENT_DEV_NAME, IFLA_PARENT_DEV_BUS_NAME, IFLA_GRO_MAX_SIZE, + IFLA_TSO_MAX_SIZE, + IFLA_TSO_MAX_SEGS, __IFLA_MAX }; diff --git a/include/uapi/linux/mptcp.h b/include/uapi/linux/mptcp.h index ca502f1e..1e07e2d9 100644 --- a/include/uapi/linux/mptcp.h +++ b/include/uapi/linux/mptcp.h @@ -53,6 +53,9 @@ enum { MPTCP_PM_ATTR_ADDR, /* nested address */ MPTCP_PM_ATTR_RCV_ADD_ADDRS, /* u32 */ MPTCP_PM_ATTR_SUBFLOWS, /* u32 */ + MPTCP_PM_ATTR_TOKEN, /* u32 */ + MPTCP_PM_ATTR_LOC_ID, /* u8 */ + MPTCP_PM_ATTR_ADDR_REMOTE, /* nested address */ __MPTCP_PM_ATTR_MAX }; @@ -91,6 +94,10 @@ enum { MPTCP_PM_CMD_SET_LIMITS, MPTCP_PM_CMD_GET_LIMITS, MPTCP_PM_CMD_SET_FLAGS, + MPTCP_PM_CMD_ANNOUNCE, + MPTCP_PM_CMD_REMOVE, + MPTCP_PM_CMD_SUBFLOW_CREATE, + MPTCP_PM_CMD_SUBFLOW_DESTROY, __MPTCP_PM_CMD_AFTER_LAST }; @@ -186,6 +193,7 @@ enum mptcp_event_attr { MPTCP_ATTR_IF_IDX, /* s32 */ MPTCP_ATTR_RESET_REASON,/* u32 */ MPTCP_ATTR_RESET_FLAGS, /* u32 */ + MPTCP_ATTR_SERVER_SIDE, /* u8 */ __MPTCP_ATTR_AFTER_LAST }; diff --git a/include/uapi/linux/neighbour.h b/include/uapi/linux/neighbour.h index db05fb55..39c565e4 100644 --- a/include/uapi/linux/neighbour.h +++ b/include/uapi/linux/neighbour.h @@ -32,6 +32,8 @@ enum { NDA_NH_ID, NDA_FDB_EXT_ATTRS, NDA_FLAGS_EXT, + NDA_NDM_STATE_MASK, + NDA_NDM_FLAGS_MASK, __NDA_MAX }; diff --git a/include/uapi/linux/netlink.h b/include/uapi/linux/netlink.h index e83e2e30..105b79f0 100644 --- a/include/uapi/linux/netlink.h +++ b/include/uapi/linux/netlink.h @@ -72,6 +72,7 @@ struct nlmsghdr { /* Modifiers to DELETE request */ #define NLM_F_NONREC 0x100 /* Do not delete recursively */ +#define NLM_F_BULK 0x200 /* Delete multiple objects */ /* Flags for ACK message */ #define NLM_F_CAPPED 0x100 /* request was capped */ diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h index 404f97fb..9a2ee1e3 100644 --- a/include/uapi/linux/pkt_cls.h +++ b/include/uapi/linux/pkt_cls.h @@ -587,6 +587,8 @@ enum { TCA_FLOWER_KEY_HASH, /* u32 */ TCA_FLOWER_KEY_HASH_MASK, /* u32 */ + TCA_FLOWER_KEY_NUM_OF_VLANS, /* u8 */ + __TCA_FLOWER_MAX, }; diff --git a/include/uapi/linux/tc_act/tc_skbedit.h b/include/uapi/linux/tc_act/tc_skbedit.h index 800e9337..6cb61012 100644 --- a/include/uapi/linux/tc_act/tc_skbedit.h +++ b/include/uapi/linux/tc_act/tc_skbedit.h @@ -29,6 +29,7 @@ #define SKBEDIT_F_PTYPE 0x8 #define SKBEDIT_F_MASK 0x10 #define SKBEDIT_F_INHERITDSFIELD 0x20 +#define SKBEDIT_F_TXQ_SKBHASH 0x40 struct tc_skbedit { tc_gen; @@ -45,6 +46,7 @@ enum { TCA_SKBEDIT_PTYPE, TCA_SKBEDIT_MASK, TCA_SKBEDIT_FLAGS, + TCA_SKBEDIT_QUEUE_MAPPING_MAX, __TCA_SKBEDIT_MAX }; #define TCA_SKBEDIT_MAX (__TCA_SKBEDIT_MAX - 1) diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h index 3ad54af2..83a3cea4 100644 --- a/include/uapi/linux/tls.h +++ b/include/uapi/linux/tls.h @@ -39,6 +39,7 @@ /* TLS socket options */ #define TLS_TX 1 /* Set transmit parameters */ #define TLS_RX 2 /* Set receive parameters */ +#define TLS_TX_ZEROCOPY_SENDFILE 3 /* transmit zerocopy sendfile */ /* Supported versions */ #define TLS_VERSION_MINOR(ver) ((ver) & 0xFF) @@ -160,6 +161,7 @@ enum { TLS_INFO_CIPHER, TLS_INFO_TXCONF, TLS_INFO_RXCONF, + TLS_INFO_ZC_SENDFILE, __TLS_INFO_MAX, }; #define TLS_INFO_MAX (__TLS_INFO_MAX - 1) diff --git a/include/uapi/linux/types.h b/include/uapi/linux/types.h index 7f957444..6546100a 100644 --- a/include/uapi/linux/types.h +++ b/include/uapi/linux/types.h @@ -21,6 +21,9 @@ #define __bitwise #endif +/* The kernel doesn't use this legacy form, but user space does */ +#define __bitwise__ __bitwise + typedef __u16 __bitwise __le16; typedef __u16 __bitwise __be16; typedef __u32 __bitwise __le32; diff --git a/ip/Makefile b/ip/Makefile index 0f14c609..6c2e0720 100644 --- a/ip/Makefile +++ b/ip/Makefile @@ -12,7 +12,8 @@ IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \ iplink_geneve.o iplink_vrf.o iproute_lwtunnel.o ipmacsec.o ipila.o \ ipvrf.o iplink_xstats.o ipseg6.o iplink_netdevsim.o iplink_rmnet.o \ ipnexthop.o ipmptcp.o iplink_bareudp.o iplink_wwan.o ipioam6.o \ - iplink_amt.o iplink_batadv.o iplink_gtp.o + iplink_amt.o iplink_batadv.o iplink_gtp.o iplink_virt_wifi.o \ + ipstats.o RTMONOBJ=rtmon.o @@ -123,6 +123,7 @@ static const struct cmd { { "mptcp", do_mptcp }, { "ioam", do_ioam6 }, { "help", do_help }, + { "stats", do_ipstats }, { 0 } }; diff --git a/ip/ip_common.h b/ip/ip_common.h index ea04c8ff..ffa633e0 100644 --- a/ip/ip_common.h +++ b/ip/ip_common.h @@ -3,6 +3,7 @@ #define _IP_COMMON_H_ #include <stdbool.h> +#include <linux/mpls.h> #include "json_print.h" @@ -57,6 +58,7 @@ int print_nexthop_bucket(struct nlmsghdr *n, void *arg); void netns_map_init(void); void netns_nsid_socket_init(void); int print_nsid(struct nlmsghdr *n, void *arg); +int ipstats_print(struct nlmsghdr *n, void *arg); char *get_name_from_nsid(int nsid); int get_netnsid_from_name(const char *name); int set_netnsid_from_name(const char *name, int nsid); @@ -90,6 +92,7 @@ int do_seg6(int argc, char **argv); int do_ipnh(int argc, char **argv); int do_mptcp(int argc, char **argv); int do_ioam6(int argc, char **argv); +int do_ipstats(int argc, char **argv); int iplink_get(char *name, __u32 filt_mask); int iplink_ifla_xstats(int argc, char **argv); @@ -139,9 +142,14 @@ int iplink_parse(int argc, char **argv, struct iplink_req *req, char **type); void br_dump_bridge_id(const struct ifla_bridge_id *id, char *buf, size_t len); int bridge_parse_xstats(struct link_util *lu, int argc, char **argv); int bridge_print_xstats(struct nlmsghdr *n, void *arg); +extern const struct ipstats_stat_desc ipstats_stat_desc_xstats_bridge_group; +extern const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_bridge_group; +/* iplink_bond.c */ int bond_parse_xstats(struct link_util *lu, int argc, char **argv); int bond_print_xstats(struct nlmsghdr *n, void *arg); +extern const struct ipstats_stat_desc ipstats_stat_desc_xstats_bond_group; +extern const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_bond_group; /* iproute_lwtunnel.c */ int lwt_parse_encap(struct rtattr *rta, size_t len, int *argcp, char ***argvp, @@ -157,6 +165,46 @@ void xdp_dump(FILE *fp, struct rtattr *tb, bool link, bool details); __u32 ipvrf_get_table(const char *name); int name_is_vrf(const char *name); +/* ipstats.c */ +enum ipstats_stat_desc_kind { + IPSTATS_STAT_DESC_KIND_LEAF, + IPSTATS_STAT_DESC_KIND_GROUP, +}; + +struct ipstats_stat_dump_filters; +struct ipstats_stat_show_attrs; + +struct ipstats_stat_desc { + const char *name; + enum ipstats_stat_desc_kind kind; + union { + struct { + const struct ipstats_stat_desc **subs; + size_t nsubs; + }; + struct { + void (*pack)(struct ipstats_stat_dump_filters *filters, + const struct ipstats_stat_desc *desc); + int (*show)(struct ipstats_stat_show_attrs *attrs, + const struct ipstats_stat_desc *desc); + }; + }; +}; + +struct ipstats_stat_desc_xstats { + const struct ipstats_stat_desc desc; + int xstats_at; + int link_type_at; + int inner_max; + int inner_at; + void (*show_cb)(const struct rtattr *at); +}; + +void ipstats_stat_desc_pack_xstats(struct ipstats_stat_dump_filters *filters, + const struct ipstats_stat_desc *desc); +int ipstats_stat_desc_show_xstats(struct ipstats_stat_show_attrs *attrs, + const struct ipstats_stat_desc *desc); + #ifndef INFINITY_LIFE_TIME #define INFINITY_LIFE_TIME 0xFFFFFFFFU #endif @@ -171,4 +219,9 @@ void print_rta_ifidx(FILE *fp, __u32 ifidx, const char *prefix); void __print_rta_gateway(FILE *fp, unsigned char family, const char *gateway); void print_rta_gateway(FILE *fp, unsigned char family, const struct rtattr *rta); +void size_columns(unsigned int cols[], unsigned int n, ...); +void print_stats64(FILE *fp, struct rtnl_link_stats64 *s, + const struct rtattr *carrier_changes, const char *what); +void print_mpls_link_stats(FILE *fp, const struct mpls_link_stats *stats, + const char *indent); #endif /* _IP_COMMON_H_ */ diff --git a/ip/ipaddress.c b/ip/ipaddress.c index a80996ef..17341d28 100644 --- a/ip/ipaddress.c +++ b/ip/ipaddress.c @@ -546,7 +546,7 @@ static void print_vfinfo(FILE *fp, struct ifinfomsg *ifi, struct rtattr *vfinfo) print_vf_stats64(fp, vf[IFLA_VF_STATS]); } -static void size_columns(unsigned int cols[], unsigned int n, ...) +void size_columns(unsigned int cols[], unsigned int n, ...) { unsigned int i, len; uint64_t val, powi; @@ -680,10 +680,10 @@ static void print_vf_stats64(FILE *fp, struct rtattr *vfstats) } } -static void __print_link_stats(FILE *fp, struct rtattr *tb[]) +void print_stats64(FILE *fp, struct rtnl_link_stats64 *s, + const struct rtattr *carrier_changes, + const char *what) { - const struct rtattr *carrier_changes = tb[IFLA_CARRIER_CHANGES]; - struct rtnl_link_stats64 _s, *s = &_s; unsigned int cols[] = { strlen("*X errors:"), strlen("packets"), @@ -693,14 +693,10 @@ static void __print_link_stats(FILE *fp, struct rtattr *tb[]) strlen("overrun"), strlen("compressed"), }; - int ret; - - ret = get_rtnl_link_stats_rta(s, tb); - if (ret < 0) - return; if (is_json_context()) { - open_json_object((ret == sizeof(*s)) ? "stats64" : "stats"); + if (what) + open_json_object(what); /* RX stats */ open_json_object("rx"); @@ -771,7 +767,8 @@ static void __print_link_stats(FILE *fp, struct rtattr *tb[]) } close_json_object(); - close_json_object(); + if (what) + close_json_object(); } else { size_columns(cols, ARRAY_SIZE(cols), s->rx_bytes, s->rx_packets, s->rx_errors, @@ -870,6 +867,20 @@ static void __print_link_stats(FILE *fp, struct rtattr *tb[]) } } +static void __print_link_stats(FILE *fp, struct rtattr *tb[]) +{ + const struct rtattr *carrier_changes = tb[IFLA_CARRIER_CHANGES]; + struct rtnl_link_stats64 _s, *s = &_s; + int ret; + + ret = get_rtnl_link_stats_rta(s, tb); + if (ret < 0) + return; + + print_stats64(fp, s, carrier_changes, + (ret == sizeof(*s)) ? "stats64" : "stats"); +} + static void print_link_stats(FILE *fp, struct nlmsghdr *n) { struct ifinfomsg *ifi = NLMSG_DATA(n); diff --git a/ip/iplink.c b/ip/iplink.c index a30dde0e..c64721bc 100644 --- a/ip/iplink.c +++ b/ip/iplink.c @@ -54,7 +54,7 @@ void iplink_types_usage(void) " macsec | macvlan | macvtap |\n" " netdevsim | nlmon | rmnet | sit | team | team_slave |\n" " vcan | veth | vlan | vrf | vti | vxcan | vxlan | wwan |\n" - " xfrm }\n"); + " xfrm | virt_wifi }\n"); } void iplink_usage(void) @@ -1514,6 +1514,65 @@ static int do_set(int argc, char **argv) } #endif /* IPLINK_IOCTL_COMPAT */ +void print_mpls_link_stats(FILE *fp, const struct mpls_link_stats *stats, + const char *indent) +{ + unsigned int cols[] = { + strlen("*X: bytes"), + strlen("packets"), + strlen("errors"), + strlen("dropped"), + strlen("noroute"), + }; + + if (is_json_context()) { + /* RX stats */ + open_json_object("rx"); + print_u64(PRINT_JSON, "bytes", NULL, stats->rx_bytes); + print_u64(PRINT_JSON, "packets", NULL, stats->rx_packets); + print_u64(PRINT_JSON, "errors", NULL, stats->rx_errors); + print_u64(PRINT_JSON, "dropped", NULL, stats->rx_dropped); + print_u64(PRINT_JSON, "noroute", NULL, stats->rx_noroute); + close_json_object(); + + /* TX stats */ + open_json_object("tx"); + print_u64(PRINT_JSON, "bytes", NULL, stats->tx_bytes); + print_u64(PRINT_JSON, "packets", NULL, stats->tx_packets); + print_u64(PRINT_JSON, "errors", NULL, stats->tx_errors); + print_u64(PRINT_JSON, "dropped", NULL, stats->tx_dropped); + close_json_object(); + } else { + size_columns(cols, ARRAY_SIZE(cols), stats->rx_bytes, + stats->rx_packets, stats->rx_errors, + stats->rx_dropped, stats->rx_noroute); + size_columns(cols, ARRAY_SIZE(cols), stats->tx_bytes, + stats->tx_packets, stats->tx_errors, + stats->tx_dropped, 0); + + fprintf(fp, "%sRX: %*s %*s %*s %*s %*s%s", indent, + cols[0] - 4, "bytes", cols[1], "packets", + cols[2], "errors", cols[3], "dropped", + cols[4], "noroute", _SL_); + fprintf(fp, "%s", indent); + print_num(fp, cols[0], stats->rx_bytes); + print_num(fp, cols[1], stats->rx_packets); + print_num(fp, cols[2], stats->rx_errors); + print_num(fp, cols[3], stats->rx_dropped); + print_num(fp, cols[4], stats->rx_noroute); + fprintf(fp, "\n"); + + fprintf(fp, "%sTX: %*s %*s %*s %*s%s", indent, + cols[0] - 4, "bytes", cols[1], "packets", + cols[2], "errors", cols[3], "dropped", _SL_); + fprintf(fp, "%s", indent); + print_num(fp, cols[0], stats->tx_bytes); + print_num(fp, cols[1], stats->tx_packets); + print_num(fp, cols[2], stats->tx_errors); + print_num(fp, cols[3], stats->tx_dropped); + } +} + static void print_mpls_stats(FILE *fp, struct rtattr *attr) { struct rtattr *mrtb[MPLS_STATS_MAX+1]; @@ -1525,22 +1584,8 @@ static void print_mpls_stats(FILE *fp, struct rtattr *attr) return; stats = RTA_DATA(mrtb[MPLS_STATS_LINK]); - fprintf(fp, " mpls:\n"); - fprintf(fp, " RX: bytes packets errors dropped noroute\n"); - fprintf(fp, " "); - print_num(fp, 10, stats->rx_bytes); - print_num(fp, 8, stats->rx_packets); - print_num(fp, 7, stats->rx_errors); - print_num(fp, 8, stats->rx_dropped); - print_num(fp, 7, stats->rx_noroute); - fprintf(fp, "\n"); - fprintf(fp, " TX: bytes packets errors dropped\n"); - fprintf(fp, " "); - print_num(fp, 10, stats->tx_bytes); - print_num(fp, 8, stats->tx_packets); - print_num(fp, 7, stats->tx_errors); - print_num(fp, 7, stats->tx_dropped); + print_mpls_link_stats(fp, stats, " "); fprintf(fp, "\n"); } @@ -1641,7 +1686,8 @@ static int iplink_afstats(int argc, char **argv) } } - if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) { + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { perror("Cannont send dump request"); return 1; } diff --git a/ip/iplink_bond.c b/ip/iplink_bond.c index 650411fc..15db19a3 100644 --- a/ip/iplink_bond.c +++ b/ip/iplink_bond.c @@ -15,6 +15,7 @@ #include <string.h> #include <linux/if_bonding.h> +#include "list.h" #include "rt_names.h" #include "utils.h" #include "ip_common.h" @@ -761,7 +762,7 @@ static void bond_print_xstats_help(struct link_util *lu, FILE *f) fprintf(f, "Usage: ... %s [ 802.3ad ] [ dev DEVICE ]\n", lu->id); } -static void bond_print_3ad_stats(struct rtattr *lacpattr) +static void bond_print_3ad_stats(const struct rtattr *lacpattr) { struct rtattr *lacptb[BOND_3AD_STAT_MAX+1]; __u64 val; @@ -912,7 +913,6 @@ int bond_parse_xstats(struct link_util *lu, int argc, char **argv) return 0; } - struct link_util bond_link_util = { .id = "bond", .maxattr = IFLA_BOND_MAX, @@ -922,3 +922,54 @@ struct link_util bond_link_util = { .parse_ifla_xstats = bond_parse_xstats, .print_ifla_xstats = bond_print_xstats, }; + +static const struct ipstats_stat_desc ipstats_stat_desc_bond_tmpl_lacp = { + .name = "802.3ad", + .kind = IPSTATS_STAT_DESC_KIND_LEAF, + .show = &ipstats_stat_desc_show_xstats, + .pack = &ipstats_stat_desc_pack_xstats, +}; + +static const struct ipstats_stat_desc_xstats +ipstats_stat_desc_xstats_bond_lacp = { + .desc = ipstats_stat_desc_bond_tmpl_lacp, + .xstats_at = IFLA_STATS_LINK_XSTATS, + .link_type_at = LINK_XSTATS_TYPE_BOND, + .inner_max = BOND_XSTATS_MAX, + .inner_at = BOND_XSTATS_3AD, + .show_cb = &bond_print_3ad_stats, +}; + +static const struct ipstats_stat_desc * +ipstats_stat_desc_xstats_bond_subs[] = { + &ipstats_stat_desc_xstats_bond_lacp.desc, +}; + +const struct ipstats_stat_desc ipstats_stat_desc_xstats_bond_group = { + .name = "bond", + .kind = IPSTATS_STAT_DESC_KIND_GROUP, + .subs = ipstats_stat_desc_xstats_bond_subs, + .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_bond_subs), +}; + +static const struct ipstats_stat_desc_xstats +ipstats_stat_desc_xstats_slave_bond_lacp = { + .desc = ipstats_stat_desc_bond_tmpl_lacp, + .xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE, + .link_type_at = LINK_XSTATS_TYPE_BOND, + .inner_max = BOND_XSTATS_MAX, + .inner_at = BOND_XSTATS_3AD, + .show_cb = &bond_print_3ad_stats, +}; + +static const struct ipstats_stat_desc * +ipstats_stat_desc_xstats_slave_bond_subs[] = { + &ipstats_stat_desc_xstats_slave_bond_lacp.desc, +}; + +const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_bond_group = { + .name = "bond", + .kind = IPSTATS_STAT_DESC_KIND_GROUP, + .subs = ipstats_stat_desc_xstats_slave_bond_subs, + .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_slave_bond_subs), +}; diff --git a/ip/iplink_bridge.c b/ip/iplink_bridge.c index c2e63f6e..3feb6109 100644 --- a/ip/iplink_bridge.c +++ b/ip/iplink_bridge.c @@ -714,11 +714,140 @@ static void bridge_print_xstats_help(struct link_util *lu, FILE *f) fprintf(f, "Usage: ... %s [ igmp ] [ dev DEVICE ]\n", lu->id); } +static void bridge_print_stats_mcast(const struct rtattr *attr) +{ + struct br_mcast_stats *mstats; + + mstats = RTA_DATA(attr); + open_json_object("multicast"); + open_json_object("igmp_queries"); + print_string(PRINT_FP, NULL, + "%-16s IGMP queries:\n", ""); + print_string(PRINT_FP, NULL, "%-16s ", ""); + print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ", + mstats->igmp_v1queries[BR_MCAST_DIR_RX]); + print_u64(PRINT_ANY, "rx_v2", "v2 %llu ", + mstats->igmp_v2queries[BR_MCAST_DIR_RX]); + print_u64(PRINT_ANY, "rx_v3", "v3 %llu\n", + mstats->igmp_v3queries[BR_MCAST_DIR_RX]); + print_string(PRINT_FP, NULL, "%-16s ", ""); + print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ", + mstats->igmp_v1queries[BR_MCAST_DIR_TX]); + print_u64(PRINT_ANY, "tx_v2", "v2 %llu ", + mstats->igmp_v2queries[BR_MCAST_DIR_TX]); + print_u64(PRINT_ANY, "tx_v3", "v3 %llu\n", + mstats->igmp_v3queries[BR_MCAST_DIR_TX]); + close_json_object(); + + open_json_object("igmp_reports"); + print_string(PRINT_FP, NULL, + "%-16s IGMP reports:\n", ""); + print_string(PRINT_FP, NULL, "%-16s ", ""); + print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ", + mstats->igmp_v1reports[BR_MCAST_DIR_RX]); + print_u64(PRINT_ANY, "rx_v2", "v2 %llu ", + mstats->igmp_v2reports[BR_MCAST_DIR_RX]); + print_u64(PRINT_ANY, "rx_v3", "v3 %llu\n", + mstats->igmp_v3reports[BR_MCAST_DIR_RX]); + print_string(PRINT_FP, NULL, "%-16s ", ""); + print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ", + mstats->igmp_v1reports[BR_MCAST_DIR_TX]); + print_u64(PRINT_ANY, "tx_v2", "v2 %llu ", + mstats->igmp_v2reports[BR_MCAST_DIR_TX]); + print_u64(PRINT_ANY, "tx_v3", "v3 %llu\n", + mstats->igmp_v3reports[BR_MCAST_DIR_TX]); + close_json_object(); + + open_json_object("igmp_leaves"); + print_string(PRINT_FP, NULL, + "%-16s IGMP leaves: ", ""); + print_u64(PRINT_ANY, "rx", "RX: %llu ", + mstats->igmp_leaves[BR_MCAST_DIR_RX]); + print_u64(PRINT_ANY, "tx", "TX: %llu\n", + mstats->igmp_leaves[BR_MCAST_DIR_TX]); + close_json_object(); + + print_string(PRINT_FP, NULL, + "%-16s IGMP parse errors: ", ""); + print_u64(PRINT_ANY, "igmp_parse_errors", "%llu\n", + mstats->igmp_parse_errors); + + open_json_object("mld_queries"); + print_string(PRINT_FP, NULL, + "%-16s MLD queries:\n", ""); + print_string(PRINT_FP, NULL, "%-16s ", ""); + print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ", + mstats->mld_v1queries[BR_MCAST_DIR_RX]); + print_u64(PRINT_ANY, "rx_v2", "v2 %llu\n", + mstats->mld_v2queries[BR_MCAST_DIR_RX]); + print_string(PRINT_FP, NULL, "%-16s ", ""); + print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ", + mstats->mld_v1queries[BR_MCAST_DIR_TX]); + print_u64(PRINT_ANY, "tx_v2", "v2 %llu\n", + mstats->mld_v2queries[BR_MCAST_DIR_TX]); + close_json_object(); + + open_json_object("mld_reports"); + print_string(PRINT_FP, NULL, + "%-16s MLD reports:\n", ""); + print_string(PRINT_FP, NULL, "%-16s ", ""); + print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ", + mstats->mld_v1reports[BR_MCAST_DIR_RX]); + print_u64(PRINT_ANY, "rx_v2", "v2 %llu\n", + mstats->mld_v2reports[BR_MCAST_DIR_RX]); + print_string(PRINT_FP, NULL, "%-16s ", ""); + print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ", + mstats->mld_v1reports[BR_MCAST_DIR_TX]); + print_u64(PRINT_ANY, "tx_v2", "v2 %llu\n", + mstats->mld_v2reports[BR_MCAST_DIR_TX]); + close_json_object(); + + open_json_object("mld_leaves"); + print_string(PRINT_FP, NULL, + "%-16s MLD leaves: ", ""); + print_u64(PRINT_ANY, "rx", "RX: %llu ", + mstats->mld_leaves[BR_MCAST_DIR_RX]); + print_u64(PRINT_ANY, "tx", "TX: %llu\n", + mstats->mld_leaves[BR_MCAST_DIR_TX]); + close_json_object(); + + print_string(PRINT_FP, NULL, + "%-16s MLD parse errors: ", ""); + print_u64(PRINT_ANY, "mld_parse_errors", "%llu\n", + mstats->mld_parse_errors); + close_json_object(); +} + +static void bridge_print_stats_stp(const struct rtattr *attr) +{ + struct bridge_stp_xstats *sstats; + + sstats = RTA_DATA(attr); + open_json_object("stp"); + print_string(PRINT_FP, NULL, + "%-16s STP BPDU: ", ""); + print_u64(PRINT_ANY, "rx_bpdu", "RX: %llu ", + sstats->rx_bpdu); + print_u64(PRINT_ANY, "tx_bpdu", "TX: %llu\n", + sstats->tx_bpdu); + print_string(PRINT_FP, NULL, + "%-16s STP TCN: ", ""); + print_u64(PRINT_ANY, "rx_tcn", "RX: %llu ", + sstats->rx_tcn); + print_u64(PRINT_ANY, "tx_tcn", "TX: %llu\n", + sstats->tx_tcn); + print_string(PRINT_FP, NULL, + "%-16s STP Transitions: ", ""); + print_u64(PRINT_ANY, "transition_blk", "Blocked: %llu ", + sstats->transition_blk); + print_u64(PRINT_ANY, "transition_fwd", "Forwarding: %llu\n", + sstats->transition_fwd); + close_json_object(); +} + static void bridge_print_stats_attr(struct rtattr *attr, int ifindex) { struct rtattr *brtb[LINK_XSTATS_TYPE_MAX+1]; - struct bridge_stp_xstats *sstats; - struct br_mcast_stats *mstats; struct rtattr *i, *list; const char *ifname = ""; int rem; @@ -738,127 +867,10 @@ static void bridge_print_stats_attr(struct rtattr *attr, int ifindex) continue; switch (i->rta_type) { case BRIDGE_XSTATS_MCAST: - mstats = RTA_DATA(i); - open_json_object("multicast"); - open_json_object("igmp_queries"); - print_string(PRINT_FP, NULL, - "%-16s IGMP queries:\n", ""); - print_string(PRINT_FP, NULL, "%-16s ", ""); - print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ", - mstats->igmp_v1queries[BR_MCAST_DIR_RX]); - print_u64(PRINT_ANY, "rx_v2", "v2 %llu ", - mstats->igmp_v2queries[BR_MCAST_DIR_RX]); - print_u64(PRINT_ANY, "rx_v3", "v3 %llu\n", - mstats->igmp_v3queries[BR_MCAST_DIR_RX]); - print_string(PRINT_FP, NULL, "%-16s ", ""); - print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ", - mstats->igmp_v1queries[BR_MCAST_DIR_TX]); - print_u64(PRINT_ANY, "tx_v2", "v2 %llu ", - mstats->igmp_v2queries[BR_MCAST_DIR_TX]); - print_u64(PRINT_ANY, "tx_v3", "v3 %llu\n", - mstats->igmp_v3queries[BR_MCAST_DIR_TX]); - close_json_object(); - - open_json_object("igmp_reports"); - print_string(PRINT_FP, NULL, - "%-16s IGMP reports:\n", ""); - print_string(PRINT_FP, NULL, "%-16s ", ""); - print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ", - mstats->igmp_v1reports[BR_MCAST_DIR_RX]); - print_u64(PRINT_ANY, "rx_v2", "v2 %llu ", - mstats->igmp_v2reports[BR_MCAST_DIR_RX]); - print_u64(PRINT_ANY, "rx_v3", "v3 %llu\n", - mstats->igmp_v3reports[BR_MCAST_DIR_RX]); - print_string(PRINT_FP, NULL, "%-16s ", ""); - print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ", - mstats->igmp_v1reports[BR_MCAST_DIR_TX]); - print_u64(PRINT_ANY, "tx_v2", "v2 %llu ", - mstats->igmp_v2reports[BR_MCAST_DIR_TX]); - print_u64(PRINT_ANY, "tx_v3", "v3 %llu\n", - mstats->igmp_v3reports[BR_MCAST_DIR_TX]); - close_json_object(); - - open_json_object("igmp_leaves"); - print_string(PRINT_FP, NULL, - "%-16s IGMP leaves: ", ""); - print_u64(PRINT_ANY, "rx", "RX: %llu ", - mstats->igmp_leaves[BR_MCAST_DIR_RX]); - print_u64(PRINT_ANY, "tx", "TX: %llu\n", - mstats->igmp_leaves[BR_MCAST_DIR_TX]); - close_json_object(); - - print_string(PRINT_FP, NULL, - "%-16s IGMP parse errors: ", ""); - print_u64(PRINT_ANY, "igmp_parse_errors", "%llu\n", - mstats->igmp_parse_errors); - - open_json_object("mld_queries"); - print_string(PRINT_FP, NULL, - "%-16s MLD queries:\n", ""); - print_string(PRINT_FP, NULL, "%-16s ", ""); - print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ", - mstats->mld_v1queries[BR_MCAST_DIR_RX]); - print_u64(PRINT_ANY, "rx_v2", "v2 %llu\n", - mstats->mld_v2queries[BR_MCAST_DIR_RX]); - print_string(PRINT_FP, NULL, "%-16s ", ""); - print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ", - mstats->mld_v1queries[BR_MCAST_DIR_TX]); - print_u64(PRINT_ANY, "tx_v2", "v2 %llu\n", - mstats->mld_v2queries[BR_MCAST_DIR_TX]); - close_json_object(); - - open_json_object("mld_reports"); - print_string(PRINT_FP, NULL, - "%-16s MLD reports:\n", ""); - print_string(PRINT_FP, NULL, "%-16s ", ""); - print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ", - mstats->mld_v1reports[BR_MCAST_DIR_RX]); - print_u64(PRINT_ANY, "rx_v2", "v2 %llu\n", - mstats->mld_v2reports[BR_MCAST_DIR_RX]); - print_string(PRINT_FP, NULL, "%-16s ", ""); - print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ", - mstats->mld_v1reports[BR_MCAST_DIR_TX]); - print_u64(PRINT_ANY, "tx_v2", "v2 %llu\n", - mstats->mld_v2reports[BR_MCAST_DIR_TX]); - close_json_object(); - - open_json_object("mld_leaves"); - print_string(PRINT_FP, NULL, - "%-16s MLD leaves: ", ""); - print_u64(PRINT_ANY, "rx", "RX: %llu ", - mstats->mld_leaves[BR_MCAST_DIR_RX]); - print_u64(PRINT_ANY, "tx", "TX: %llu\n", - mstats->mld_leaves[BR_MCAST_DIR_TX]); - close_json_object(); - - print_string(PRINT_FP, NULL, - "%-16s MLD parse errors: ", ""); - print_u64(PRINT_ANY, "mld_parse_errors", "%llu\n", - mstats->mld_parse_errors); - close_json_object(); + bridge_print_stats_mcast(i); break; case BRIDGE_XSTATS_STP: - sstats = RTA_DATA(i); - open_json_object("stp"); - print_string(PRINT_FP, NULL, - "%-16s STP BPDU: ", ""); - print_u64(PRINT_ANY, "rx_bpdu", "RX: %llu ", - sstats->rx_bpdu); - print_u64(PRINT_ANY, "tx_bpdu", "TX: %llu\n", - sstats->tx_bpdu); - print_string(PRINT_FP, NULL, - "%-16s STP TCN: ", ""); - print_u64(PRINT_ANY, "rx_tcn", "RX: %llu ", - sstats->rx_tcn); - print_u64(PRINT_ANY, "tx_tcn", "TX: %llu\n", - sstats->tx_tcn); - print_string(PRINT_FP, NULL, - "%-16s STP Transitions: ", ""); - print_u64(PRINT_ANY, "transition_blk", "Blocked: %llu ", - sstats->transition_blk); - print_u64(PRINT_ANY, "transition_fwd", "Forwarding: %llu\n", - sstats->transition_fwd); - close_json_object(); + bridge_print_stats_stp(i); break; } } @@ -924,3 +936,83 @@ struct link_util bridge_link_util = { .parse_ifla_xstats = bridge_parse_xstats, .print_ifla_xstats = bridge_print_xstats, }; + +static const struct ipstats_stat_desc ipstats_stat_desc_bridge_tmpl_stp = { + .name = "stp", + .kind = IPSTATS_STAT_DESC_KIND_LEAF, + .show = &ipstats_stat_desc_show_xstats, + .pack = &ipstats_stat_desc_pack_xstats, +}; + +static const struct ipstats_stat_desc ipstats_stat_desc_bridge_tmpl_mcast = { + .name = "mcast", + .kind = IPSTATS_STAT_DESC_KIND_LEAF, + .show = &ipstats_stat_desc_show_xstats, + .pack = &ipstats_stat_desc_pack_xstats, +}; + +static const struct ipstats_stat_desc_xstats +ipstats_stat_desc_xstats_bridge_stp = { + .desc = ipstats_stat_desc_bridge_tmpl_stp, + .xstats_at = IFLA_STATS_LINK_XSTATS, + .link_type_at = LINK_XSTATS_TYPE_BRIDGE, + .inner_max = BRIDGE_XSTATS_MAX, + .inner_at = BRIDGE_XSTATS_STP, + .show_cb = &bridge_print_stats_stp, +}; + +static const struct ipstats_stat_desc_xstats +ipstats_stat_desc_xstats_bridge_mcast = { + .desc = ipstats_stat_desc_bridge_tmpl_mcast, + .xstats_at = IFLA_STATS_LINK_XSTATS, + .link_type_at = LINK_XSTATS_TYPE_BRIDGE, + .inner_max = BRIDGE_XSTATS_MAX, + .inner_at = BRIDGE_XSTATS_MCAST, + .show_cb = &bridge_print_stats_mcast, +}; + +static const struct ipstats_stat_desc * +ipstats_stat_desc_xstats_bridge_subs[] = { + &ipstats_stat_desc_xstats_bridge_stp.desc, + &ipstats_stat_desc_xstats_bridge_mcast.desc, +}; + +const struct ipstats_stat_desc ipstats_stat_desc_xstats_bridge_group = { + .name = "bridge", + .kind = IPSTATS_STAT_DESC_KIND_GROUP, + .subs = ipstats_stat_desc_xstats_bridge_subs, + .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_bridge_subs), +}; + +static const struct ipstats_stat_desc_xstats +ipstats_stat_desc_xstats_slave_bridge_stp = { + .desc = ipstats_stat_desc_bridge_tmpl_stp, + .xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE, + .link_type_at = LINK_XSTATS_TYPE_BRIDGE, + .inner_max = BRIDGE_XSTATS_MAX, + .inner_at = BRIDGE_XSTATS_STP, + .show_cb = &bridge_print_stats_stp, +}; + +static const struct ipstats_stat_desc_xstats +ipstats_stat_desc_xstats_slave_bridge_mcast = { + .desc = ipstats_stat_desc_bridge_tmpl_mcast, + .xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE, + .link_type_at = LINK_XSTATS_TYPE_BRIDGE, + .inner_max = BRIDGE_XSTATS_MAX, + .inner_at = BRIDGE_XSTATS_MCAST, + .show_cb = &bridge_print_stats_mcast, +}; + +static const struct ipstats_stat_desc * +ipstats_stat_desc_xstats_slave_bridge_subs[] = { + &ipstats_stat_desc_xstats_slave_bridge_stp.desc, + &ipstats_stat_desc_xstats_slave_bridge_mcast.desc, +}; + +const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_bridge_group = { + .name = "bridge", + .kind = IPSTATS_STAT_DESC_KIND_GROUP, + .subs = ipstats_stat_desc_xstats_slave_bridge_subs, + .nsubs = ARRAY_SIZE(ipstats_stat_desc_xstats_slave_bridge_subs), +}; diff --git a/ip/iplink_virt_wifi.c b/ip/iplink_virt_wifi.c new file mode 100644 index 00000000..8d3054cd --- /dev/null +++ b/ip/iplink_virt_wifi.c @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * iplink_virt_wifi.c A fake implementation of cfg80211_ops that can be tacked + * on to an ethernet net_device to make it appear as a + * wireless connection. + * + * Authors: Baligh Gasmi <gasmibal@gmail.com> + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "utils.h" +#include "ip_common.h" + +static void virt_wifi_print_help(struct link_util *lu, + int argc, char **argv, FILE *f) +{ + fprintf(f, "Usage: ... virt_wifi \n"); +} + +struct link_util virt_wifi_link_util = { + .id = "virt_wifi", + .print_help = virt_wifi_print_help, +}; diff --git a/ip/iplink_vxlan.c b/ip/iplink_vxlan.c index 9afa3cca..01522d6e 100644 --- a/ip/iplink_vxlan.c +++ b/ip/iplink_vxlan.c @@ -48,6 +48,7 @@ static void print_explain(FILE *f) " [ [no]udp6zerocsumrx ]\n" " [ [no]remcsumtx ] [ [no]remcsumrx ]\n" " [ [no]external ] [ gbp ] [ gpe ]\n" + " [ [no]vnifilter ]\n" "\n" "Where: VNI := 0-16777215\n" " ADDR := { IP_ADDRESS | any }\n" @@ -81,6 +82,7 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv, __u8 learning = 1; __u16 dstport = 0; __u8 metadata = 0; + __u8 vnifilter = 0; __u64 attrs = 0; bool set_op = (n->nlmsg_type == RTM_NEWLINK && !(n->nlmsg_flags & NLM_F_CREATE)); @@ -330,6 +332,15 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv, } else if (!matches(*argv, "gpe")) { check_duparg(&attrs, IFLA_VXLAN_GPE, *argv, *argv); addattr_l(n, 1024, IFLA_VXLAN_GPE, NULL, 0); + } else if (!strcmp(*argv, "vnifilter")) { + check_duparg(&attrs, IFLA_VXLAN_VNIFILTER, + *argv, *argv); + addattr8(n, 1024, IFLA_VXLAN_VNIFILTER, 1); + vnifilter = 1; + } else if (!strcmp(*argv, "novnifilter")) { + check_duparg(&attrs, IFLA_VXLAN_VNIFILTER, + *argv, *argv); + addattr8(n, 1024, IFLA_VXLAN_VNIFILTER, 0); } else if (matches(*argv, "help") == 0) { explain(); return -1; @@ -341,12 +352,17 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv, argc--, argv++; } + if (!metadata && vnifilter) { + fprintf(stderr, "vxlan: vnifilter is valid only when 'external' is set\n"); + return -1; + } + if (metadata && VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID)) { fprintf(stderr, "vxlan: both 'external' and vni cannot be specified\n"); return -1; } - if (!metadata && !VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID) && !set_op) { + if (!metadata && !vnifilter && !VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID) && !set_op) { fprintf(stderr, "vxlan: missing virtual network identifier\n"); return -1; } @@ -420,6 +436,11 @@ static void vxlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) print_bool(PRINT_ANY, "external", "external ", true); } + if (tb[IFLA_VXLAN_VNIFILTER] && + rta_getattr_u8(tb[IFLA_VXLAN_VNIFILTER])) { + print_bool(PRINT_ANY, "vnifilter", "vnifilter", true); + } + if (tb[IFLA_VXLAN_ID] && RTA_PAYLOAD(tb[IFLA_VXLAN_ID]) >= sizeof(__u32)) { print_uint(PRINT_ANY, "id", "id %u ", rta_getattr_u32(tb[IFLA_VXLAN_ID])); diff --git a/ip/iplink_xstats.c b/ip/iplink_xstats.c index c64e6885..1d180b0b 100644 --- a/ip/iplink_xstats.c +++ b/ip/iplink_xstats.c @@ -65,7 +65,8 @@ int iplink_ifla_xstats(int argc, char **argv) else filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS); - if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) { + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { perror("Cannont send dump request"); return -1; } diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c index 0badda4e..a4326d2a 100644 --- a/ip/ipmonitor.c +++ b/ip/ipmonitor.c @@ -34,7 +34,7 @@ static void usage(void) "Usage: ip monitor [ all | OBJECTS ] [ FILE ] [ label ] [ all-nsid ]\n" " [ dev DEVICE ]\n" "OBJECTS := address | link | mroute | neigh | netconf |\n" - " nexthop | nsid | prefix | route | rule\n" + " nexthop | nsid | prefix | route | rule | stats\n" "FILE := file FILENAME\n"); exit(-1); } @@ -158,6 +158,11 @@ static int accept_msg(struct rtnl_ctrl_data *ctrl, print_nsid(n, arg); return 0; + case RTM_NEWSTATS: + print_headers(fp, "[STATS]", ctrl); + ipstats_print(n, arg); + return 0; + case NLMSG_ERROR: case NLMSG_NOOP: case NLMSG_DONE: @@ -185,6 +190,7 @@ int do_ipmonitor(int argc, char **argv) int lprefix = 0; int lneigh = 0; int lnetconf = 0; + int lstats = 0; int lrule = 0; int lnsid = 0; int ifindex = 0; @@ -253,6 +259,9 @@ int do_ipmonitor(int argc, char **argv) } else if (matches(*argv, "nexthop") == 0) { lnexthop = 1; groups = 0; + } else if (strcmp(*argv, "stats") == 0) { + lstats = 1; + groups = 0; } else if (strcmp(*argv, "all") == 0) { prefix_banner = 1; } else if (matches(*argv, "all-nsid") == 0) { @@ -349,6 +358,11 @@ int do_ipmonitor(int argc, char **argv) exit(1); } + if (lstats && rtnl_add_nl_group(&rth, RTNLGRP_STATS) < 0) { + fprintf(stderr, "Failed to add stats group to list\n"); + exit(1); + } + if (listen_all_nsid && rtnl_listen_all_nsid(&rth) < 0) exit(1); diff --git a/ip/ipstats.c b/ip/ipstats.c new file mode 100644 index 00000000..5cdd15ae --- /dev/null +++ b/ip/ipstats.c @@ -0,0 +1,1356 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include <assert.h> +#include <errno.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) + 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; +} diff --git a/lib/libnetlink.c b/lib/libnetlink.c index 6d1b1187..c27627fe 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -619,12 +619,13 @@ int rtnl_fdb_linkdump_req_filter_fn(struct rtnl_handle *rth, return send(rth->fd, &req, sizeof(req), 0); } -int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask) +int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, + __u32 filt_mask, + int (*filter_fn)(struct ipstats_req *req, + void *data), + void *filter_data) { - struct { - struct nlmsghdr nlh; - struct if_stats_msg ifsm; - } req; + struct ipstats_req req; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg)); @@ -635,6 +636,14 @@ int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask) req.ifsm.family = fam; req.ifsm.filter_mask = filt_mask; + if (filter_fn) { + int err; + + err = filter_fn(&req, filter_data); + if (err) + return err; + } + return send(rth->fd, &req, sizeof(req), 0); } @@ -1600,3 +1609,23 @@ void nl_print_policy(const struct rtattr *attr, FILE *fp) } } } + +int rtnl_tunneldump_req(struct rtnl_handle *rth, int family, int ifindex, + __u8 flags) +{ + struct { + struct nlmsghdr nlh; + struct tunnel_msg tmsg; + char buf[256]; + } req = { + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tunnel_msg)), + .nlh.nlmsg_type = RTM_GETTUNNEL, + .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, + .nlh.nlmsg_seq = rth->dump = ++rth->seq, + .tmsg.family = family, + .tmsg.flags = flags, + .tmsg.ifindex = ifindex, + }; + + return send(rth->fd, &req, sizeof(req), 0); +} diff --git a/man/man8/bridge.8 b/man/man8/bridge.8 index 2fa4f3d6..d8923d2e 100644 --- a/man/man8/bridge.8 +++ b/man/man8/bridge.8 @@ -13,7 +13,7 @@ bridge \- show / manipulate bridge addresses and devices .ti -8 .IR OBJECT " := { " -.BR link " | " fdb " | " mdb " | " vlan " | " monitor " }" +.BR link " | " fdb " | " mdb " | " vlan " | " vni " | " monitor " }" .sp .ti -8 @@ -197,6 +197,25 @@ bridge \- show / manipulate bridge addresses and devices .IR VID " ]" .ti -8 +.BR "bridge vlan" " show " [ " +.B dev +.IR DEV " ]" + +.ti -8 +.BR "bridge vni" " { " add " | " del " } " +.B dev +.I DEV +.B vni +.IR VNI " [ { " +.B group | remote "} " +.IR IPADDR " ] " + +.ti -8 +.BR "bridge vni" " show " [ " +.B dev +.IR DEV " ]" + +.ti -8 .BR "bridge monitor" " [ " all " | " neigh " | " link " | " mdb " | " vlan " ]" .SH OPTIONS @@ -303,6 +322,10 @@ the output. .B vlan - VLAN filter list. +.TP +.B vni +- VNI filter list. + .SS .I COMMAND @@ -1084,6 +1107,58 @@ all bridge interfaces. the VLAN ID only whose global options should be listed. Default is to list all vlans. +.SH bridge vni - VNI filter list + +.B vni +objects contain known VNI IDs for a dst metadata vxlan link. + +.P +The corresponding commands display vni filter entries, add new entries, +and delete old ones. + +.SS bridge vni add - add a new vni filter entry + +This command creates a new vni filter entry. + +.TP +.BI dev " NAME" +the interface with which this vni is associated. + +.TP +.BI vni " VNI" +the VNI ID that identifies the vni. + +.TP +.BI remote " IPADDR" +specifies the unicast destination IP address to use in outgoing packets +when the destination link layer address is not known in the VXLAN device +forwarding database. This parameter cannot be specified with the group. + +.TP +.BI group " IPADDR" +specifies the multicast IP address to join for this VNI + +.SS bridge vni del - delete a new vni filter entry + +This command removes an existing vni filter entry. + +.PP +The arguments are the same as with +.BR "bridge vni add". + +.SS bridge vni show - list vni filtering configuration. + +This command displays the current vni filter table. + +.PP +With the +.B -statistics +option, the command displays per-vni traffic statistics. + +.TP +.BI dev " NAME" +shows vni filtering table associated with the vxlan device + .SH bridge monitor - state monitoring The diff --git a/man/man8/devlink.8 b/man/man8/devlink.8 index 840cf44c..de53061b 100644 --- a/man/man8/devlink.8 +++ b/man/man8/devlink.8 @@ -63,6 +63,10 @@ Switches to the specified network namespace. .BR "\-i", " --iec" Print human readable rates in IEC units (e.g. 1Ki = 1024). +.TP +.BR "\-x", " --hex" +Print dump numbers in hexadecimal format. + .SS .I OBJECT diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in index ee189abc..6f332645 100644 --- a/man/man8/ip-link.8.in +++ b/man/man8/ip-link.8.in @@ -209,42 +209,43 @@ ip-link \- network device configuration .ti -8 .IR TYPE " := [ " .BR amt " | " -.BR bridge " | " +.BR bareudp " |" .BR bond " | " +.BR bridge " | " .BR can " | " .BR dummy " | " -.BR hsr " | " -.BR ifb " | " -.BR ipoib " |" -.BR macvlan " | " -.BR macvtap " | " -.BR vcan " | " -.BR vxcan " | " -.BR veth " | " -.BR vlan " | " -.BR vxlan " |" -.BR ip6tnl " |" -.BR ipip " |" -.BR sit " |" +.BR erspan " |" +.BR geneve " |" .BR gre " |" .BR gretap " |" -.BR erspan " |" +.BR gtp " |" +.BR hsr " | " +.BR ifb " | " +.BR ip6erspan " |" .BR ip6gre " |" .BR ip6gretap " |" -.BR ip6erspan " |" -.BR vti " |" -.BR nlmon " |" +.BR ip6tnl " |" +.BR ipip " |" +.BR ipoib " |" .BR ipvlan " |" .BR ipvtap " |" .BR lowpan " |" -.BR geneve " |" -.BR bareudp " |" -.BR vrf " |" .BR macsec " |" +.BR macvlan " | " +.BR macvtap " | " .BR netdevsim " |" +.BR nlmon " |" .BR rmnet " |" -.BR xfrm " |" -.BR gtp " ]" +.BR sit " |" +.BR vcan " | " +.BR veth " | " +.BR virt_wifi " |" +.BR vlan " | " +.BR vrf " |" +.BR vti " |" +.BR vxcan " | " +.BR vxlan " |" +.BR xfrm " ]" .ti -8 .IR ETYPE " := [ " TYPE " |" @@ -289,44 +290,52 @@ specifies the type of the new device. Link types: .in +8 -.B bridge -- Ethernet Bridge device +.BR amt +- Automatic Multicast Tunneling (AMT) +.sp +.BR bareudp +- Bare UDP L3 encapsulation support .sp .B bond - Bonding device +.B bridge +- Ethernet Bridge device +.sp +.B can +- Controller Area Network .sp .B dummy - Dummy network interface .sp -.B hsr -- High-availability Seamless Redundancy device +.BR erspan +- Encapsulated Remote SPAN over GRE and IPv4 .sp -.B ifb -- Intermediate Functional Block device +.B geneve +- GEneric NEtwork Virtualization Encapsulation .sp -.B ipoib -- IP over Infiniband device +.B gre +- Virtual tunnel interface GRE over IPv4 .sp -.B macvlan -- Virtual interface base on link layer address (MAC) +.BR gretap +- Virtual L2 tunnel interface GRE over IPv4 .sp -.B macvtap -- Virtual interface based on link layer address (MAC) and TAP. +.BR gtp +- GPRS Tunneling Protocol .sp -.B vcan -- Virtual Controller Area Network interface +.B hsr +- High-availability Seamless Redundancy device .sp -.B vxcan -- Virtual Controller Area Network tunnel interface +.B ifb +- Intermediate Functional Block device .sp -.B veth -- Virtual ethernet interface +.BR ip6erspan +- Encapsulated Remote SPAN over GRE and IPv6 .sp -.BR vlan -- 802.1q tagged virtual LAN interface +.BR ip6gre +- Virtual tunnel interface GRE over IPv6 .sp -.BR vxlan -- Virtual eXtended LAN +.BR ip6gretap +- Virtual L2 tunnel interface GRE over IPv6 .sp .BR ip6tnl - Virtual tunnel interface IPv4|IPv6 over IPv6 @@ -334,68 +343,66 @@ Link types: .BR ipip - Virtual tunnel interface IPv4 over IPv4 .sp -.BR sit -- Virtual tunnel interface IPv6 over IPv4 +.B ipoib +- IP over Infiniband device .sp -.BR gre -- Virtual tunnel interface GRE over IPv4 +.BR ipvlan +- Interface for L3 (IPv6/IPv4) based VLANs .sp -.BR gretap -- Virtual L2 tunnel interface GRE over IPv4 +.BR ipvtap +- Interface for L3 (IPv6/IPv4) based VLANs and TAP .sp -.BR erspan -- Encapsulated Remote SPAN over GRE and IPv4 +.BR lowpan +- Interface for 6LoWPAN (IPv6) over IEEE 802.15.4 / Bluetooth .sp -.BR ip6gre -- Virtual tunnel interface GRE over IPv6 +.BR macsec +- Interface for IEEE 802.1AE MAC Security (MACsec) .sp -.BR ip6gretap -- Virtual L2 tunnel interface GRE over IPv6 +.B macvlan +- Virtual interface base on link layer address (MAC) .sp -.BR ip6erspan -- Encapsulated Remote SPAN over GRE and IPv6 +.B macvtap +- Virtual interface based on link layer address (MAC) and TAP. .sp -.BR vti -- Virtual tunnel interface +.BR netdevsim +- Interface for netdev API tests .sp .BR nlmon - Netlink monitoring device .sp -.BR ipvlan -- Interface for L3 (IPv6/IPv4) based VLANs -.sp -.BR ipvtap -- Interface for L3 (IPv6/IPv4) based VLANs and TAP +.BR rmnet +- Qualcomm rmnet device .sp -.BR lowpan -- Interface for 6LoWPAN (IPv6) over IEEE 802.15.4 / Bluetooth +.BR sit +- Virtual tunnel interface IPv6 over IPv4 .sp -.BR geneve -- GEneric NEtwork Virtualization Encapsulation +.B vcan +- Virtual Controller Area Network interface .sp -.BR bareudp -- Bare UDP L3 encapsulation support +.B veth +- Virtual ethernet interface .sp -.BR amt -- Automatic Multicast Tunneling (AMT) +.BR virt_wifi +- rtnetlink wifi simulation device .sp -.BR macsec -- Interface for IEEE 802.1AE MAC Security (MACsec) +.BR vlan +- 802.1q tagged virtual LAN interface .sp .BR vrf - Interface for L3 VRF domains .sp -.BR netdevsim -- Interface for netdev API tests +.BR vti +- Virtual tunnel interface .sp -.BR rmnet -- Qualcomm rmnet device +.B vxcan +- Virtual Controller Area Network tunnel interface +.sp +.BR vxlan +- Virtual eXtended LAN .sp .BR xfrm - Virtual xfrm interface .sp -.BR gtp -- GPRS Tunneling Protocol .in -8 .TP @@ -594,6 +601,8 @@ the following additional arguments are supported: .B gbp ] [ .B gpe +] [ +.RB [ no ] vnifilter ] .in +8 @@ -706,6 +715,13 @@ are entered into the VXLAN device forwarding database. or the internal FDB should be used. .sp +.RB [ no ] vnifilter +- specifies whether the vxlan device is capable of vni filtering. Only works with a vxlan +device with external flag set. once enabled, bridge vni command is used to manage the +vni filtering table on the device. The device can only receive packets with vni's configured +in the vni filtering table. + +.sp .B gbp - enables the Group Policy extension (VXLAN-GBP). diff --git a/man/man8/ip-monitor.8 b/man/man8/ip-monitor.8 index f886d31b..ec033c69 100644 --- a/man/man8/ip-monitor.8 +++ b/man/man8/ip-monitor.8 @@ -55,7 +55,7 @@ command is the first in the command line and then the object list follows: is the list of object types that we want to monitor. It may contain .BR link ", " address ", " route ", " mroute ", " prefix ", " -.BR neigh ", " netconf ", " rule ", " nsid " and " nexthop "." +.BR neigh ", " netconf ", " rule ", " stats ", " nsid " and " nexthop "." If no .B file argument is given, diff --git a/man/man8/ip-stats.8 b/man/man8/ip-stats.8 new file mode 100644 index 00000000..26336454 --- /dev/null +++ b/man/man8/ip-stats.8 @@ -0,0 +1,208 @@ +.TH IP\-STATS 8 "16 Mar 2022" "iproute2" "Linux" +.SH NAME +ip-stats \- manage and show interface statistics +.SH SYNOPSIS +.sp +.ad l +.in +8 +.ti -8 +.B ip +.B stats +.RI " { " COMMAND " | " +.BR help " }" +.sp + +.ti -8 +.BR "ip stats show" +.RB "[ " dev +.IR DEV " ] " +.RB "[ " group +.IR GROUP " [ " +.BI subgroup " SUBGROUP" +.RB " [ " suite +.IR " SUITE" " ] ... ] ... ] ..." + +.ti -8 +.BR "ip stats set" +.BI dev " DEV" +.BR l3_stats " { " +.BR on " | " off " }" + +.SH DESCRIPTION + +.TP +.B ip stats set +is used for toggling whether a certain HW statistics suite is collected on +a given netdevice. The following statistics suites are supported: + +.in 21 + +.ti 14 +.B l3_stats +L3 stats reflect traffic that takes place in a HW device on an object that +corresponds to the given software netdevice. + +.TP +.B ip stats show +is used for showing stats on a given netdevice, or dumping statistics +across all netdevices. By default, all stats are requested. It is possible +to filter which stats are requested by using the +.B group +and +.B subgroup +keywords. + +It is possible to specify several groups, or several subgroups for one +group. When no subgroups are given for a group, all the subgroups are +requested. + +The following groups are recognized: +.in 21 + +.ti 14 +.B group link +- Link statistics. The same suite that "ip -s link show" shows. + +.ti 14 +.B group offload +- A group that contains a number of HW-oriented statistics. See below for +individual subgroups within this group. + +.ti 14 +.B group xstats +- Extended statistics. A subgroup identifies the type of netdevice to show the +statistics for. + +.ti 14 +.B group xstats_slave +- Extended statistics for the slave of a netdevice of a given type. A subgroup +identifies the type of master netdevice. + +.ti 14 +.B group afstats +- A group for address-family specific netdevice statistics. + +.TQ +.BR "group offload " subgroups: +.in 21 + +.ti 14 +.B subgroup cpu_hit +- The +.B cpu_hit +statistics suite is useful on hardware netdevices. The +.B link +statistics on these devices reflect both the hardware- and +software-datapath traffic. The +.B cpu_hit +statistics then only reflect software-datapath traffic. + +.ti 14 +.B subgroup hw_stats_info +- This suite does not include traffic statistics, but rather communicates +the state of other statistics. Through this subgroup, it is possible to +discover whether a given statistic was enabled, and when it was, whether +any device driver actually configured its device to collect these +statistics. For example, +.B l3_stats +was enabled in the following case, but no driver has installed it: + +# ip stats show dev swp1 group offload subgroup hw_stats_info +.br +56: swp1: group offload subgroup hw_stats_info +.br + l3_stats on used off + +After an L3 address is added to the netdevice, the counter will be +installed: + +# ip addr add dev swp1 192.0.2.1/28 +.br +# ip stats show dev swp1 group offload subgroup hw_stats_info +.br +56: swp1: group offload subgroup hw_stats_info +.br + l3_stats on used on + +.ti 14 +.B subgroup l3_stats +- These statistics reflect L3 traffic that takes place in HW on an object +that corresponds to the netdevice. Note that this suite is disabled by +default and needs to be first enabled through +.B ip stats set\fR. + +For example: + +# ip stats show dev swp2.200 group offload subgroup l3_stats +.br +112: swp2.200: group offload subgroup l3_stats on used on +.br + RX: bytes packets errors dropped mcast +.br + 8900 72 2 0 3 +.br + TX: bytes packets errors dropped +.br + 7176 58 0 0 + +Note how the l3_stats_info for the selected group is also part of the dump. + +.TQ +.BR "group xstats " and " group xstats_slave " subgroups: +.in 21 + +.ti 14 +.B subgroup bridge \fR[\fB suite stp \fR] [\fB suite mcast \fR] +- Statistics for STP and, respectively, IGMP / MLD (under the keyword +\fBmcast\fR) traffic on bridges and their slaves. + +.ti 14 +.B subgroup bond \fR[\fB suite 802.3ad \fR] +- Statistics for LACP traffic on bond devices and their slaves. + +.TQ +.BR "group afstats " subgroups: +.in 21 + +.ti 14 +.B subgroup mpls +- Statistics for MPLS traffic seen on the netdevice. For example: + +# ip stats show dev veth01 group afstats subgroup mpls +.br +3: veth01: group afstats subgroup mpls +.br + RX: bytes packets errors dropped noroute +.br + 0 0 0 0 0 +.br + TX: bytes packets errors dropped +.br + 216 2 0 0 + +.SH EXAMPLES +.PP +# ip stats set dev swp1 l3_stats on +.RS +Enables collection of L3 HW statistics on swp1. +.RE + +.PP +# ip stats show group offload +.RS +Shows all offload statistics on all netdevices. +.RE + +.PP +# ip stats show dev swp1 group link +.RS +Shows link statistics on the given netdevice. +.RE + +.SH SEE ALSO +.br +.BR ip (8), +.BR ip-link (8), + +.SH AUTHOR +Manpage by Petr Machata. diff --git a/man/man8/ip.8 b/man/man8/ip.8 index 2a4848b7..f6adbc77 100644 --- a/man/man8/ip.8 +++ b/man/man8/ip.8 @@ -22,7 +22,7 @@ ip \- show / manipulate routing, network devices, interfaces and tunnels .BR link " | " address " | " addrlabel " | " route " | " rule " | " neigh " | "\ ntable " | " tunnel " | " tuntap " | " maddress " | " mroute " | " mrule " | "\ monitor " | " xfrm " | " netns " | " l2tp " | " tcp_metrics " | " token " | "\ - macsec " | " vrf " | " mptcp " | " ioam " }" + macsec " | " vrf " | " mptcp " | " ioam " | " stats " }" .sp .ti -8 @@ -303,6 +303,10 @@ readability. - rule in routing policy database. .TP +.B stats +- manage and show interface statistics. + +.TP .B tcp_metrics/tcpmetrics - manage TCP Metrics @@ -419,6 +423,7 @@ was written by Alexey N. Kuznetsov and added in Linux 2.2. .BR ip-ntable (8), .BR ip-route (8), .BR ip-rule (8), +.BR ip-stats (8) .BR ip-tcp_metrics (8), .BR ip-token (8), .BR ip-tunnel (8), diff --git a/man/man8/tc-flower.8 b/man/man8/tc-flower.8 index f918a06d..52393524 100644 --- a/man/man8/tc-flower.8 +++ b/man/man8/tc-flower.8 @@ -164,6 +164,11 @@ provided in LLADDR format, in which case it is a bitwise mask, or as a number of high bits to match. If the mask is missing then a match on all bits is assumed. .TP +.BI num_of_vlans " NUM" +Match on the number of vlan tags in the packet. +.I NUM +can be 0 or small positive integer. Typically in 0-4 range. +.TP .BI vlan_id " VID" Match on vlan tag id. .I VID diff --git a/misc/ifstat.c b/misc/ifstat.c index d4a33429..291f288b 100644 --- a/misc/ifstat.c +++ b/misc/ifstat.c @@ -202,7 +202,7 @@ static void load_info(void) ll_init_map(&rth); filter_mask = IFLA_STATS_FILTER_BIT(filter_type); if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, - filter_mask) < 0) { + filter_mask, NULL, NULL) < 0) { perror("Cannot send dump request"); exit(1); } diff --git a/tc/f_flower.c b/tc/f_flower.c index 686cf121..805ca671 100644 --- a/tc/f_flower.c +++ b/tc/f_flower.c @@ -48,6 +48,7 @@ static void explain(void) "\n" "Where: MATCH-LIST := [ MATCH-LIST ] MATCH\n" " MATCH := { indev DEV-NAME |\n" + " num_of_vlans VLANS_COUNT |\n" " vlan_id VID |\n" " vlan_prio PRIORITY |\n" " vlan_ethtype [ ipv4 | ipv6 | ETH-TYPE ] |\n" @@ -159,21 +160,23 @@ err: return err; } -static bool eth_type_vlan(__be16 ethertype) +static bool eth_type_vlan(__be16 ethertype, bool good_num_of_vlans) { return ethertype == htons(ETH_P_8021Q) || - ethertype == htons(ETH_P_8021AD); + ethertype == htons(ETH_P_8021AD) || + good_num_of_vlans; } static int flower_parse_vlan_eth_type(char *str, __be16 eth_type, int type, __be16 *p_vlan_eth_type, - struct nlmsghdr *n) + struct nlmsghdr *n, bool good_num_of_vlans) { __be16 vlan_eth_type; - if (!eth_type_vlan(eth_type)) { - fprintf(stderr, "Can't set \"%s\" if ethertype isn't 802.1Q or 802.1AD\n", - type == TCA_FLOWER_KEY_VLAN_ETH_TYPE ? "vlan_ethtype" : "cvlan_ethtype"); + if (!eth_type_vlan(eth_type, good_num_of_vlans)) { + fprintf(stderr, "Can't set \"%s\" if ethertype isn't 802.1Q or 802.1AD and num_of_vlans %s\n", + type == TCA_FLOWER_KEY_VLAN_ETH_TYPE ? "vlan_ethtype" : "cvlan_ethtype", + type == TCA_FLOWER_KEY_VLAN_ETH_TYPE ? "is 0" : "less than 2"); return -1; } @@ -1424,6 +1427,7 @@ static int flower_parse_opt(struct filter_util *qu, char *handle, __be16 tc_proto = TC_H_MIN(t->tcm_info); __be16 eth_type = tc_proto; __be16 vlan_ethtype = 0; + __u8 num_of_vlans = 0; __u8 ip_proto = 0xff; __u32 flags = 0; __u32 mtf = 0; @@ -1525,12 +1529,22 @@ static int flower_parse_opt(struct filter_util *qu, char *handle, if (check_ifname(*argv)) invarg("\"indev\" not a valid ifname", *argv); addattrstrz(n, MAX_MSG, TCA_FLOWER_INDEV, *argv); + } else if (strcmp(*argv, "num_of_vlans") == 0) { + NEXT_ARG(); + ret = get_u8(&num_of_vlans, *argv, 10); + if (ret < 0) { + fprintf(stderr, "Illegal \"num_of_vlans\"\n"); + return -1; + } + addattr8(n, MAX_MSG, + TCA_FLOWER_KEY_NUM_OF_VLANS, num_of_vlans); } else if (matches(*argv, "vlan_id") == 0) { __u16 vid; NEXT_ARG(); - if (!eth_type_vlan(tc_proto)) { - fprintf(stderr, "Can't set \"vlan_id\" if ethertype isn't 802.1Q or 802.1AD\n"); + if (!eth_type_vlan(tc_proto, num_of_vlans > 0)) { + fprintf(stderr, "Can't set \"vlan_id\" if ethertype isn't 802.1Q or 802.1AD" + " and num_of_vlans is 0\n"); return -1; } ret = get_u16(&vid, *argv, 10); @@ -1543,8 +1557,9 @@ static int flower_parse_opt(struct filter_util *qu, char *handle, __u8 vlan_prio; NEXT_ARG(); - if (!eth_type_vlan(tc_proto)) { - fprintf(stderr, "Can't set \"vlan_prio\" if ethertype isn't 802.1Q or 802.1AD\n"); + if (!eth_type_vlan(tc_proto, num_of_vlans > 0)) { + fprintf(stderr, "Can't set \"vlan_prio\" if ethertype isn't 802.1Q or 802.1AD" + " and num_of_vlans is 0\n"); return -1; } ret = get_u8(&vlan_prio, *argv, 10); @@ -1558,7 +1573,7 @@ static int flower_parse_opt(struct filter_util *qu, char *handle, NEXT_ARG(); ret = flower_parse_vlan_eth_type(*argv, eth_type, TCA_FLOWER_KEY_VLAN_ETH_TYPE, - &vlan_ethtype, n); + &vlan_ethtype, n, num_of_vlans > 0); if (ret < 0) return -1; /* get new ethtype for later parsing */ @@ -1567,8 +1582,9 @@ static int flower_parse_opt(struct filter_util *qu, char *handle, __u16 vid; NEXT_ARG(); - if (!eth_type_vlan(vlan_ethtype)) { - fprintf(stderr, "Can't set \"cvlan_id\" if inner vlan ethertype isn't 802.1Q or 802.1AD\n"); + if (!eth_type_vlan(vlan_ethtype, num_of_vlans > 1)) { + fprintf(stderr, "Can't set \"cvlan_id\" if inner vlan ethertype isn't 802.1Q or 802.1AD" + " and num_of_vlans is less than 2\n"); return -1; } ret = get_u16(&vid, *argv, 10); @@ -1581,8 +1597,9 @@ static int flower_parse_opt(struct filter_util *qu, char *handle, __u8 cvlan_prio; NEXT_ARG(); - if (!eth_type_vlan(vlan_ethtype)) { - fprintf(stderr, "Can't set \"cvlan_prio\" if inner vlan ethertype isn't 802.1Q or 802.1AD\n"); + if (!eth_type_vlan(vlan_ethtype, num_of_vlans > 1)) { + fprintf(stderr, "Can't set \"cvlan_prio\" if inner vlan ethertype isn't 802.1Q or 802.1AD" + " and num_of_vlans is less than 2\n"); return -1; } ret = get_u8(&cvlan_prio, *argv, 10); @@ -1597,7 +1614,7 @@ static int flower_parse_opt(struct filter_util *qu, char *handle, /* get new ethtype for later parsing */ ret = flower_parse_vlan_eth_type(*argv, vlan_ethtype, TCA_FLOWER_KEY_CVLAN_ETH_TYPE, - ð_type, n); + ð_type, n, num_of_vlans > 1); if (ret < 0) return -1; } else if (matches(*argv, "mpls") == 0) { @@ -2694,6 +2711,14 @@ static int flower_print_opt(struct filter_util *qu, FILE *f, open_json_object("keys"); + if (tb[TCA_FLOWER_KEY_NUM_OF_VLANS]) { + struct rtattr *attr = tb[TCA_FLOWER_KEY_NUM_OF_VLANS]; + + print_nl(); + print_uint(PRINT_ANY, "num_of_vlans", " num_of_vlans %d", + rta_getattr_u8(attr)); + } + if (tb[TCA_FLOWER_KEY_VLAN_ID]) { struct rtattr *attr = tb[TCA_FLOWER_KEY_VLAN_ID]; |