aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Hemminger <stephen@networkplumber.org>2022-05-26 17:09:59 -0700
committerStephen Hemminger <stephen@networkplumber.org>2022-05-26 17:09:59 -0700
commitb1521ec002e1d2c72404aa9e4d7734c541260a7c (patch)
tree5174bcbb5f5a80f19328ae69ebae412e8a4c2978
parent6474b7c8651368d63968671d75e34e4c7d6c9a20 (diff)
parent8d3977ef8193f976a0905d5ff054869f0d5ebc11 (diff)
downloadiproute2-b1521ec002e1d2c72404aa9e4d7734c541260a7c.tar.gz
Merge git://git.kernel.org/pub/scm/network/iproute2/iproute2-next
-rw-r--r--bridge/Makefile2
-rw-r--r--bridge/br_common.h2
-rw-r--r--bridge/bridge.c1
-rw-r--r--bridge/monitor.c28
-rw-r--r--bridge/vlan.c6
-rw-r--r--bridge/vni.c439
-rw-r--r--devlink/devlink.c19
-rw-r--r--include/libnetlink.h20
-rw-r--r--include/uapi/linux/bpf.h125
-rw-r--r--include/uapi/linux/btf.h4
-rw-r--r--include/uapi/linux/devlink.h23
-rw-r--r--include/uapi/linux/if_link.h7
-rw-r--r--include/uapi/linux/mptcp.h8
-rw-r--r--include/uapi/linux/neighbour.h2
-rw-r--r--include/uapi/linux/netlink.h1
-rw-r--r--include/uapi/linux/pkt_cls.h2
-rw-r--r--include/uapi/linux/tc_act/tc_skbedit.h2
-rw-r--r--include/uapi/linux/tls.h2
-rw-r--r--include/uapi/linux/types.h3
-rw-r--r--ip/Makefile3
-rw-r--r--ip/ip.c1
-rw-r--r--ip/ip_common.h53
-rw-r--r--ip/ipaddress.c33
-rw-r--r--ip/iplink.c80
-rw-r--r--ip/iplink_bond.c55
-rw-r--r--ip/iplink_bridge.c334
-rw-r--r--ip/iplink_virt_wifi.c25
-rw-r--r--ip/iplink_vxlan.c23
-rw-r--r--ip/iplink_xstats.c3
-rw-r--r--ip/ipmonitor.c16
-rw-r--r--ip/ipstats.c1356
-rw-r--r--lib/libnetlink.c39
-rw-r--r--man/man8/bridge.877
-rw-r--r--man/man8/devlink.84
-rw-r--r--man/man8/ip-link.8.in180
-rw-r--r--man/man8/ip-monitor.82
-rw-r--r--man/man8/ip-stats.8208
-rw-r--r--man/man8/ip.87
-rw-r--r--man/man8/tc-flower.85
-rw-r--r--misc/ifstat.c2
-rw-r--r--tc/f_flower.c57
41 files changed, 2984 insertions, 275 deletions
diff --git a/bridge/Makefile b/bridge/Makefile
index c6b7d08da..01f8a455b 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 610e83f65..841f0594a 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 f3a4f08ff..704be50c7 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 845e221ab..f17c1906b 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 8300f353f..390a2037d 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 000000000..a0c2792c7
--- /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 aab739f7f..ddf430bbb 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 9e4cc101a..a7b0f3523 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 5889a4d30..2892794fa 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 92c41b157..aa0f4a3ef 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 210d4fbff..da0f1ba8f 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 22e21e57a..da99b6437 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 ca502f1ed..1e07e2d95 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 db05fb550..39c565e46 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 e83e2e300..105b79f05 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 404f97fb2..9a2ee1e39 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 800e93377..6cb610120 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 3ad54af21..83a3cea46 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 7f957444c..6546100a6 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 0f14c609a..6c2e07204 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
diff --git a/ip/ip.c b/ip/ip.c
index c784f8191..82282babd 100644
--- a/ip/ip.c
+++ b/ip/ip.c
@@ -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 ea04c8ff3..ffa633e09 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 a80996efd..17341d287 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 a30dde0e3..c64721bc6 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 650411fc7..15db19a3d 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 c2e63f6e0..3feb6109f 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 000000000..8d3054cd4
--- /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 9afa3ccad..01522d6eb 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 c64e68856..1d180b0bd 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 0badda4e7..a4326d2ae 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 000000000..5cdd15ae0
--- /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 6d1b11876..c27627fed 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 2fa4f3d69..d8923d2eb 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 840cf44cf..de53061bc 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 ee189abc3..6f3326450 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 f886d31b8..ec033c691 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 000000000..26336454e
--- /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 2a4848b78..f6adbc772 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 f918a06d2..523935242 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 d4a33429d..291f288b3 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 686cf1214..805ca6718 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,
- &eth_type, n);
+ &eth_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];