summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Syromyatnikov <evgsyr@gmail.com>2018-08-19 13:34:41 +0200
committerDmitry V. Levin <ldv@strace.io>2021-11-07 08:00:00 +0000
commita1813471957f97e85df7098453598c57aa0bfda5 (patch)
tree2a280b596b41e881700e00a66c0658d853b53a61
parente8fb4de6d960c7a6374dbb59f20fbd3689e88df5 (diff)
downloadstrace-a1813471957f97e85df7098453598c57aa0bfda5.tar.gz
netlink_route: implement RTM_NEWSTATS and RTM_GETSTATS message decoding
* src/xlat/ifstats_af_spec_mpls_attrs.in: New file. * src/xlat/ifstats_attr_flags.in: Likewise. * src/xlat/ifstats_attrs.in: Likewise. * src/xlat/ifstats_offload_attrs.in: Likewise. * src/xlat/ifstats_xstats_bond_3ad_attrs.in: Likewise. * src/xlat/ifstats_xstats_bond_attrs.in: Likewise. * src/xlat/ifstats_xstats_bridge_attrs.in: Likewise. * src/xlat/ifstats_xstats_bridge_mcast_indices.in: Likewise. * src/xlat/ifstats_xstats_type_attrs.in: Likewise. * src/xlat/nl_bridge_vlan_flags.in: Likewise. * bundled/linux/include/uapi/linux/if_bonding.h: New file, copied from headers_install'ed Linux kernel v5.15. * bundled/linux/include/uapi/linux/mpls.h: Likewise. * bundled/Makefile.am (EXTRA_DIST): Add them. * src/rtnl_stats.c: New file. * src/Makefile.am (strace_SOURCES): Add it. * src/netlink_route.c (route_decoders) <[RTM_NEWSTATS - RTM_BASE], [RTM_GETSTATS - RTM_BASE]>: New decoder, call decode_ifstatsmsg. * src/netlink_route.h (decode_ifstatsmsg): New declaration. * src/nlattr.h (DECL_NLA(rtnl_link_stats64)): Likewise. * src/rtnl_link.c (decode_nla_rtnl_link_stats64): Rename from decode_rtnl_link_stats64. (ifinfomsg_nla_decoders) <[IFLA_STATS64]>: Use decode_nla_rtnl_link_stats64. * src/print_fields.h (PRINT_FIELD_ARRAY_INDEXED): New macro. * tests/nlattr_ifstats.c: New file. * tests/nlattr_ifstats-Xabbrev.c: Likewise. * tests/nlattr_ifstats-Xraw.c: Likewise. * tests/nlattr_ifstats-Xverbose.c: Likewise. * tests/.gitignore: Add nlattr_ifstats, nlattr_ifstats-Xabbrev, nlattr_ifstats-Xraw, and nlattr_ifstats-Xverbose. * tests/pure_executables.list: Likewise. * tests/gen_tests.in (nlattr_ifstats, nlattr_ifstats-Xabbrev, nlattr_ifstats-Xraw, nlattr_ifstats-Xverbose): New tests. * tests/netlink_route.c: Update/add checks for the RTM_*STATS message types. * NEWS: Mention it.
-rw-r--r--NEWS4
-rw-r--r--bundled/Makefile.am2
-rw-r--r--bundled/linux/include/uapi/linux/if_bonding.h155
-rw-r--r--bundled/linux/include/uapi/linux/mpls.h77
-rw-r--r--src/Makefile.am1
-rw-r--r--src/netlink_route.c4
-rw-r--r--src/netlink_route.h1
-rw-r--r--src/nlattr.h2
-rw-r--r--src/print_fields.h12
-rw-r--r--src/rtnl_link.c12
-rw-r--r--src/rtnl_stats.c438
-rw-r--r--src/xlat/ifstats_af_spec_mpls_attrs.in4
-rw-r--r--src/xlat/ifstats_attr_flags.in7
-rw-r--r--src/xlat/ifstats_attrs.in8
-rw-r--r--src/xlat/ifstats_offload_attrs.in4
-rw-r--r--src/xlat/ifstats_xstats_bond_3ad_attrs.in12
-rw-r--r--src/xlat/ifstats_xstats_bond_attrs.in4
-rw-r--r--src/xlat/ifstats_xstats_bridge_attrs.in7
-rw-r--r--src/xlat/ifstats_xstats_bridge_mcast_indices.in4
-rw-r--r--src/xlat/ifstats_xstats_type_attrs.in5
-rw-r--r--src/xlat/nl_bridge_vlan_flags.in8
-rw-r--r--tests/.gitignore4
-rw-r--r--tests/gen_tests.in4
-rw-r--r--tests/netlink_route.c57
-rw-r--r--tests/nlattr_ifstats-Xabbrev.c2
-rw-r--r--tests/nlattr_ifstats-Xraw.c2
-rw-r--r--tests/nlattr_ifstats-Xverbose.c2
-rw-r--r--tests/nlattr_ifstats.c784
-rwxr-xr-xtests/pure_executables.list4
29 files changed, 1614 insertions, 16 deletions
diff --git a/NEWS b/NEWS
index 8af0bc665..ea051e63d 100644
--- a/NEWS
+++ b/NEWS
@@ -9,8 +9,8 @@ Noteworthy changes in release ?.?? (????-??-??)
* Implemented decoding of SECCOMP_GET_NOTIF_SIZES operation of seccomp
syscall.
* Implemented decoding of SECCOMP_* ioctl commands.
- * Implemented decoding of RTM_{NEW,DEL,GET}NEXTHOP NETLINK_ROUTE netlink
- messages.
+ * Implemented decoding of RTM_{NEW,DEL,GET}NEXTHOP and RTM_{NEW,GET}STATS
+ NETLINK_ROUTE netlink messages.
* Enhanced decoding of io_uring_register and times syscalls.
* Enhanced AF_IPX socket address decoding.
* Updated lists of BTRFS_*, DM_*, FAN_REPORT_*, IORING_*, MPOL_*, PACKET_*,
diff --git a/bundled/Makefile.am b/bundled/Makefile.am
index b3da1b23a..388e05dc5 100644
--- a/bundled/Makefile.am
+++ b/bundled/Makefile.am
@@ -43,6 +43,7 @@ EXTRA_DIST = \
linux/include/uapi/linux/hiddev.h \
linux/include/uapi/linux/if_addr.h \
linux/include/uapi/linux/if_addrlabel.h \
+ linux/include/uapi/linux/if_bonding.h \
linux/include/uapi/linux/if_bridge.h \
linux/include/uapi/linux/if_link.h \
linux/include/uapi/linux/in6.h \
@@ -60,6 +61,7 @@ EXTRA_DIST = \
linux/include/uapi/linux/memfd.h \
linux/include/uapi/linux/mmtimer.h \
linux/include/uapi/linux/mount.h \
+ linux/include/uapi/linux/mpls.h \
linux/include/uapi/linux/mqueue.h \
linux/include/uapi/linux/neighbour.h \
linux/include/uapi/linux/netconf.h \
diff --git a/bundled/linux/include/uapi/linux/if_bonding.h b/bundled/linux/include/uapi/linux/if_bonding.h
new file mode 100644
index 000000000..d174914a8
--- /dev/null
+++ b/bundled/linux/include/uapi/linux/if_bonding.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-1.0+ WITH Linux-syscall-note */
+/*
+ * Bond several ethernet interfaces into a Cisco, running 'Etherchannel'.
+ *
+ *
+ * Portions are (c) Copyright 1995 Simon "Guru Aleph-Null" Janes
+ * NCM: Network and Communications Management, Inc.
+ *
+ * BUT, I'm the one who modified it for ethernet, so:
+ * (c) Copyright 1999, Thomas Davis, tadavis@lbl.gov
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU Public License, incorporated herein by reference.
+ *
+ * 2003/03/18 - Amir Noam <amir.noam at intel dot com>
+ * - Added support for getting slave's speed and duplex via ethtool.
+ * Needed for 802.3ad and other future modes.
+ *
+ * 2003/03/18 - Tsippy Mendelson <tsippy.mendelson at intel dot com> and
+ * Shmulik Hen <shmulik.hen at intel dot com>
+ * - Enable support of modes that need to use the unique mac address of
+ * each slave.
+ *
+ * 2003/03/18 - Tsippy Mendelson <tsippy.mendelson at intel dot com> and
+ * Amir Noam <amir.noam at intel dot com>
+ * - Moved driver's private data types to bonding.h
+ *
+ * 2003/03/18 - Amir Noam <amir.noam at intel dot com>,
+ * Tsippy Mendelson <tsippy.mendelson at intel dot com> and
+ * Shmulik Hen <shmulik.hen at intel dot com>
+ * - Added support for IEEE 802.3ad Dynamic link aggregation mode.
+ *
+ * 2003/05/01 - Amir Noam <amir.noam at intel dot com>
+ * - Added ABI version control to restore compatibility between
+ * new/old ifenslave and new/old bonding.
+ *
+ * 2003/12/01 - Shmulik Hen <shmulik.hen at intel dot com>
+ * - Code cleanup and style changes
+ *
+ * 2005/05/05 - Jason Gabler <jygabler at lbl dot gov>
+ * - added definitions for various XOR hashing policies
+ */
+
+#ifndef _LINUX_IF_BONDING_H
+#define _LINUX_IF_BONDING_H
+
+#include <linux/if.h>
+#include <linux/types.h>
+#include <linux/if_ether.h>
+
+/* userland - kernel ABI version (2003/05/08) */
+#define BOND_ABI_VERSION 2
+
+/*
+ * We can remove these ioctl definitions in 2.5. People should use the
+ * SIOC*** versions of them instead
+ */
+#define BOND_ENSLAVE_OLD (SIOCDEVPRIVATE)
+#define BOND_RELEASE_OLD (SIOCDEVPRIVATE + 1)
+#define BOND_SETHWADDR_OLD (SIOCDEVPRIVATE + 2)
+#define BOND_SLAVE_INFO_QUERY_OLD (SIOCDEVPRIVATE + 11)
+#define BOND_INFO_QUERY_OLD (SIOCDEVPRIVATE + 12)
+#define BOND_CHANGE_ACTIVE_OLD (SIOCDEVPRIVATE + 13)
+
+#define BOND_CHECK_MII_STATUS (SIOCGMIIPHY)
+
+#define BOND_MODE_ROUNDROBIN 0
+#define BOND_MODE_ACTIVEBACKUP 1
+#define BOND_MODE_XOR 2
+#define BOND_MODE_BROADCAST 3
+#define BOND_MODE_8023AD 4
+#define BOND_MODE_TLB 5
+#define BOND_MODE_ALB 6 /* TLB + RLB (receive load balancing) */
+
+/* each slave's link has 4 states */
+#define BOND_LINK_UP 0 /* link is up and running */
+#define BOND_LINK_FAIL 1 /* link has just gone down */
+#define BOND_LINK_DOWN 2 /* link has been down for too long time */
+#define BOND_LINK_BACK 3 /* link is going back */
+
+/* each slave has several states */
+#define BOND_STATE_ACTIVE 0 /* link is active */
+#define BOND_STATE_BACKUP 1 /* link is backup */
+
+#define BOND_DEFAULT_MAX_BONDS 1 /* Default maximum number of devices to support */
+
+#define BOND_DEFAULT_TX_QUEUES 16 /* Default number of tx queues per device */
+
+#define BOND_DEFAULT_RESEND_IGMP 1 /* Default number of IGMP membership reports */
+
+/* hashing types */
+#define BOND_XMIT_POLICY_LAYER2 0 /* layer 2 (MAC only), default */
+#define BOND_XMIT_POLICY_LAYER34 1 /* layer 3+4 (IP ^ (TCP || UDP)) */
+#define BOND_XMIT_POLICY_LAYER23 2 /* layer 2+3 (IP ^ MAC) */
+#define BOND_XMIT_POLICY_ENCAP23 3 /* encapsulated layer 2+3 */
+#define BOND_XMIT_POLICY_ENCAP34 4 /* encapsulated layer 3+4 */
+#define BOND_XMIT_POLICY_VLAN_SRCMAC 5 /* vlan + source MAC */
+
+/* 802.3ad port state definitions (43.4.2.2 in the 802.3ad standard) */
+#define LACP_STATE_LACP_ACTIVITY 0x1
+#define LACP_STATE_LACP_TIMEOUT 0x2
+#define LACP_STATE_AGGREGATION 0x4
+#define LACP_STATE_SYNCHRONIZATION 0x8
+#define LACP_STATE_COLLECTING 0x10
+#define LACP_STATE_DISTRIBUTING 0x20
+#define LACP_STATE_DEFAULTED 0x40
+#define LACP_STATE_EXPIRED 0x80
+
+typedef struct ifbond {
+ __s32 bond_mode;
+ __s32 num_slaves;
+ __s32 miimon;
+} ifbond;
+
+typedef struct ifslave {
+ __s32 slave_id; /* Used as an IN param to the BOND_SLAVE_INFO_QUERY ioctl */
+ char slave_name[IFNAMSIZ];
+ __s8 link;
+ __s8 state;
+ __u32 link_failure_count;
+} ifslave;
+
+struct ad_info {
+ __u16 aggregator_id;
+ __u16 ports;
+ __u16 actor_key;
+ __u16 partner_key;
+ __u8 partner_system[ETH_ALEN];
+};
+
+/* Embedded inside LINK_XSTATS_TYPE_BOND */
+enum {
+ BOND_XSTATS_UNSPEC,
+ BOND_XSTATS_3AD,
+ __BOND_XSTATS_MAX
+};
+#define BOND_XSTATS_MAX (__BOND_XSTATS_MAX - 1)
+
+/* Embedded inside BOND_XSTATS_3AD */
+enum {
+ BOND_3AD_STAT_LACPDU_RX,
+ BOND_3AD_STAT_LACPDU_TX,
+ BOND_3AD_STAT_LACPDU_UNKNOWN_RX,
+ BOND_3AD_STAT_LACPDU_ILLEGAL_RX,
+ BOND_3AD_STAT_MARKER_RX,
+ BOND_3AD_STAT_MARKER_TX,
+ BOND_3AD_STAT_MARKER_RESP_RX,
+ BOND_3AD_STAT_MARKER_RESP_TX,
+ BOND_3AD_STAT_MARKER_UNKNOWN_RX,
+ BOND_3AD_STAT_PAD,
+ __BOND_3AD_STAT_MAX
+};
+#define BOND_3AD_STAT_MAX (__BOND_3AD_STAT_MAX - 1)
+
+#endif /* _LINUX_IF_BONDING_H */
diff --git a/bundled/linux/include/uapi/linux/mpls.h b/bundled/linux/include/uapi/linux/mpls.h
new file mode 100644
index 000000000..9effbf99d
--- /dev/null
+++ b/bundled/linux/include/uapi/linux/mpls.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _MPLS_H
+#define _MPLS_H
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+/* Reference: RFC 5462, RFC 3032
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Label | TC |S| TTL |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Label: Label Value, 20 bits
+ * TC: Traffic Class field, 3 bits
+ * S: Bottom of Stack, 1 bit
+ * TTL: Time to Live, 8 bits
+ */
+
+struct mpls_label {
+ __be32 entry;
+};
+
+#define MPLS_LS_LABEL_MASK 0xFFFFF000
+#define MPLS_LS_LABEL_SHIFT 12
+#define MPLS_LS_TC_MASK 0x00000E00
+#define MPLS_LS_TC_SHIFT 9
+#define MPLS_LS_S_MASK 0x00000100
+#define MPLS_LS_S_SHIFT 8
+#define MPLS_LS_TTL_MASK 0x000000FF
+#define MPLS_LS_TTL_SHIFT 0
+
+/* Reserved labels */
+#define MPLS_LABEL_IPV4NULL 0 /* RFC3032 */
+#define MPLS_LABEL_RTALERT 1 /* RFC3032 */
+#define MPLS_LABEL_IPV6NULL 2 /* RFC3032 */
+#define MPLS_LABEL_IMPLNULL 3 /* RFC3032 */
+#define MPLS_LABEL_ENTROPY 7 /* RFC6790 */
+#define MPLS_LABEL_GAL 13 /* RFC5586 */
+#define MPLS_LABEL_OAMALERT 14 /* RFC3429 */
+#define MPLS_LABEL_EXTENSION 15 /* RFC7274 */
+
+#define MPLS_LABEL_FIRST_UNRESERVED 16 /* RFC3032 */
+
+/* These are embedded into IFLA_STATS_AF_SPEC:
+ * [IFLA_STATS_AF_SPEC]
+ * -> [AF_MPLS]
+ * -> [MPLS_STATS_xxx]
+ *
+ * Attributes:
+ * [MPLS_STATS_LINK] = {
+ * struct mpls_link_stats
+ * }
+ */
+enum {
+ MPLS_STATS_UNSPEC, /* also used as 64bit pad attribute */
+ MPLS_STATS_LINK,
+ __MPLS_STATS_MAX,
+};
+
+#define MPLS_STATS_MAX (__MPLS_STATS_MAX - 1)
+
+struct mpls_link_stats {
+ __u64 rx_packets; /* total packets received */
+ __u64 tx_packets; /* total packets transmitted */
+ __u64 rx_bytes; /* total bytes received */
+ __u64 tx_bytes; /* total bytes transmitted */
+ __u64 rx_errors; /* bad packets received */
+ __u64 tx_errors; /* packet transmit problems */
+ __u64 rx_dropped; /* packet dropped on receive */
+ __u64 tx_dropped; /* packet dropped on transmit */
+ __u64 rx_noroute; /* no route for packet dest */
+};
+
+#endif /* _MPLS_H */
diff --git a/src/Makefile.am b/src/Makefile.am
index a02fe98fd..d002df787 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -308,6 +308,7 @@ libstrace_a_SOURCES = \
rtnl_nsid.c \
rtnl_route.c \
rtnl_rule.c \
+ rtnl_stats.c \
rtnl_tc.c \
rtnl_tc_action.c \
s390.c \
diff --git a/src/netlink_route.c b/src/netlink_route.c
index e4b037183..45ae9b9d6 100644
--- a/src/netlink_route.c
+++ b/src/netlink_route.c
@@ -102,8 +102,8 @@ static const netlink_route_decoder_t route_decoders[] = {
[RTM_DELNSID - RTM_BASE] = decode_rtgenmsg,
[RTM_GETNSID - RTM_BASE] = decode_rtgenmsg,
- /* RTM_NEWSTATS */
- /* RTM_GETSTATS */
+ [RTM_NEWSTATS - RTM_BASE] = decode_ifstatsmsg,
+ [RTM_GETSTATS - RTM_BASE] = decode_ifstatsmsg,
/* RTM_NEWCACHEREPORT */
diff --git a/src/netlink_route.h b/src/netlink_route.h
index 3a63d1f38..7ae573100 100644
--- a/src/netlink_route.h
+++ b/src/netlink_route.h
@@ -25,6 +25,7 @@ extern DECL_NETLINK_ROUTE_DECODER(decode_fib_rule_hdr);
extern DECL_NETLINK_ROUTE_DECODER(decode_ifaddrlblmsg);
extern DECL_NETLINK_ROUTE_DECODER(decode_ifaddrmsg);
extern DECL_NETLINK_ROUTE_DECODER(decode_ifinfomsg);
+extern DECL_NETLINK_ROUTE_DECODER(decode_ifstatsmsg);
extern DECL_NETLINK_ROUTE_DECODER(decode_ndmsg);
extern DECL_NETLINK_ROUTE_DECODER(decode_ndtmsg);
extern DECL_NETLINK_ROUTE_DECODER(decode_netconfmsg);
diff --git a/src/nlattr.h b/src/nlattr.h
index 4610ba891..a7e29dc6e 100644
--- a/src/nlattr.h
+++ b/src/nlattr.h
@@ -78,6 +78,7 @@ DECL_NLA(uid);
DECL_NLA(gid);
DECL_NLA(clock_t);
DECL_NLA(ifindex);
+DECL_NLA(ifla_af_spec);
DECL_NLA(ether_proto);
DECL_NLA(ip_proto);
DECL_NLA(in_addr);
@@ -86,6 +87,7 @@ DECL_NLA(lwt_encap_type);
DECL_NLA(meminfo);
DECL_NLA(rt_class);
DECL_NLA(rt_proto);
+DECL_NLA(rtnl_link_stats64);
DECL_NLA(tc_stats);
# define NLA_HWADDR_FAMILY_OFFSET 1024
diff --git a/src/print_fields.h b/src/print_fields.h
index f0d33f629..f4b915be2 100644
--- a/src/print_fields.h
+++ b/src/print_fields.h
@@ -523,6 +523,18 @@ tprints_arg_begin(const char *name)
(print_func_)); \
} while (0)
+# define PRINT_FIELD_ARRAY_INDEXED(where_, field_, tcp_, print_func_, \
+ ind_xlat_, ind_dflt_) \
+ do { \
+ tprints_field_name(#field_); \
+ print_local_array_ex((tcp_), (where_).field_, \
+ ARRAY_SIZE((where_).field_), \
+ sizeof(((where_).field_)[0]), \
+ (print_func_), \
+ NULL, PAF_PRINT_INDICES | XLAT_STYLE_FMT_U, \
+ (ind_xlat_), (ind_dflt_)); \
+ } while (0)
+
# define PRINT_FIELD_ARRAY_UPTO(where_, field_, \
upto_, tcp_, print_func_) \
do { \
diff --git a/src/rtnl_link.c b/src/rtnl_link.c
index 2c405601c..d8a7431a5 100644
--- a/src/rtnl_link.c
+++ b/src/rtnl_link.c
@@ -465,11 +465,11 @@ decode_ifla_linkinfo(struct tcb *const tcp,
return true;
}
-static bool
-decode_rtnl_link_stats64(struct tcb *const tcp,
- const kernel_ulong_t addr,
- const unsigned int len,
- const void *const opaque_data)
+bool
+decode_nla_rtnl_link_stats64(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
{
struct rtnl_link_stats64 st;
const unsigned int min_size =
@@ -970,7 +970,7 @@ static const nla_decoder_t ifinfomsg_nla_decoders[] = {
[IFLA_IFALIAS] = decode_nla_str,
[IFLA_NUM_VF] = decode_nla_u32,
[IFLA_VFINFO_LIST] = NULL, /* unimplemented */
- [IFLA_STATS64] = decode_rtnl_link_stats64,
+ [IFLA_STATS64] = decode_nla_rtnl_link_stats64,
[IFLA_VF_PORTS] = decode_ifla_vf_ports,
[IFLA_PORT_SELF] = decode_ifla_port,
[IFLA_AF_SPEC] = decode_ifla_af_spec,
diff --git a/src/rtnl_stats.c b/src/rtnl_stats.c
new file mode 100644
index 000000000..5e2ca7e3f
--- /dev/null
+++ b/src/rtnl_stats.c
@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2018-2021 Eugene Syromyatnikov <evgsyr@gmail.com>
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "defs.h"
+#include "netlink_route.h"
+#include "nlattr.h"
+#include "print_fields.h"
+
+#include "netlink.h"
+
+#include <netinet/in.h>
+
+#include <linux/if_bonding.h>
+#include <linux/if_bridge.h>
+#include <linux/if_link.h>
+#include <linux/mpls.h>
+#include <linux/rtnetlink.h>
+
+#include "xlat/ifstats_af_spec_mpls_attrs.h"
+#include "xlat/ifstats_attrs.h"
+#include "xlat/ifstats_attr_flags.h"
+#include "xlat/ifstats_offload_attrs.h"
+#include "xlat/ifstats_xstats_bond_attrs.h"
+#include "xlat/ifstats_xstats_bond_3ad_attrs.h"
+#include "xlat/ifstats_xstats_bridge_attrs.h"
+#include "xlat/ifstats_xstats_bridge_mcast_indices.h"
+#include "xlat/ifstats_xstats_type_attrs.h"
+#include "xlat/nl_bridge_vlan_flags.h"
+
+#define XLAT_MACROS_ONLY
+# include "xlat/addrfams.h" /* AF_MPLS */
+#undef XLAT_MACROS_ONLY
+
+static bool
+decode_ifstats_link_xstats_bridge_vlan(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ struct bridge_vlan_xstats st;
+
+ if (len < sizeof(st))
+ return false;
+
+ if (umove_or_printaddr(tcp, addr, &st))
+ return true;
+
+ tprint_struct_begin();
+ PRINT_FIELD_U(st, rx_bytes);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, rx_packets);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, tx_bytes);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, tx_packets);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, vid);
+ tprint_struct_next();
+ PRINT_FIELD_FLAGS(st, flags, nl_bridge_vlan_flags,
+ "BRIDGE_VLAN_INFO_???");
+ if (st.pad2) {
+ tprint_struct_next();
+ PRINT_FIELD_X(st, pad2);
+ }
+ tprint_struct_end();
+
+ if (len > sizeof(st)) {
+ tprint_array_next();
+ printstr_ex(tcp, addr + sizeof(st), len - sizeof(st),
+ QUOTE_FORCE_HEX);
+ }
+
+ return true;
+}
+
+static bool
+decode_ifstats_link_xstats_bridge_mcast(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ struct br_mcast_stats st;
+
+ if (len < sizeof(st))
+ return false;
+
+ if (umove_or_printaddr(tcp, addr, &st))
+ return true;
+
+#define PRINT_FIELD_MCAST_ARRAY_(where_, field_) \
+ PRINT_FIELD_ARRAY_INDEXED(where_, field_, \
+ tcp, print_uint_array_member, \
+ ifstats_xstats_bridge_mcast_indices, \
+ NULL);
+
+ tprint_struct_begin();
+ PRINT_FIELD_MCAST_ARRAY_(st, igmp_v1queries);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, igmp_v2queries);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, igmp_v3queries);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, igmp_leaves);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, igmp_v1reports);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, igmp_v2reports);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, igmp_v3reports);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, igmp_parse_errors);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, mld_v1queries);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, mld_v2queries);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, mld_leaves);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, mld_v1reports);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, mld_v2reports);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, mld_parse_errors);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, mcast_bytes);
+ tprint_struct_next();
+ PRINT_FIELD_MCAST_ARRAY_(st, mcast_packets);
+ tprint_struct_end();
+
+#undef PRINT_FIELD_MCAST_ARRAY_
+
+ if (len > sizeof(st)) {
+ tprint_array_next();
+ printstr_ex(tcp, addr + sizeof(st), len - sizeof(st),
+ QUOTE_FORCE_HEX);
+ }
+
+ return true;
+}
+
+static bool
+decode_ifstats_link_xstats_bridge_stp(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ struct bridge_stp_xstats st;
+
+ if (len < sizeof(st))
+ return false;
+
+ if (umove_or_printaddr(tcp, addr, &st))
+ return true;
+
+ tprint_struct_begin();
+ PRINT_FIELD_U(st, transition_blk);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, transition_fwd);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, rx_bpdu);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, tx_bpdu);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, rx_tcn);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, tx_tcn);
+ tprint_struct_end();
+
+ if (len > sizeof(st)) {
+ tprint_array_next();
+ printstr_ex(tcp, addr + sizeof(st), len - sizeof(st),
+ QUOTE_FORCE_HEX);
+ }
+
+ return true;
+}
+
+static const nla_decoder_t ifstats_xstats_bridge_decoders[] = {
+ [BRIDGE_XSTATS_UNSPEC] = NULL,
+ [BRIDGE_XSTATS_VLAN] = decode_ifstats_link_xstats_bridge_vlan,
+ [BRIDGE_XSTATS_MCAST] = decode_ifstats_link_xstats_bridge_mcast,
+ [BRIDGE_XSTATS_PAD] = NULL,
+ [BRIDGE_XSTATS_STP] = decode_ifstats_link_xstats_bridge_stp,
+};
+
+static bool
+decode_ifstats_link_xstats_bridge(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ decode_nlattr(tcp, addr, len, ifstats_xstats_bridge_attrs,
+ "BRIDGE_XSTATS_???",
+ ARRSZ_PAIR(ifstats_xstats_bridge_decoders),
+ opaque_data);
+
+ return true;
+}
+
+static const nla_decoder_t ifstats_xstats_bond_3ad_decoders[] = {
+ [BOND_3AD_STAT_LACPDU_RX] = decode_nla_u64,
+ [BOND_3AD_STAT_LACPDU_TX] = decode_nla_u64,
+ [BOND_3AD_STAT_LACPDU_UNKNOWN_RX] = decode_nla_u64,
+ [BOND_3AD_STAT_LACPDU_ILLEGAL_RX] = decode_nla_u64,
+ [BOND_3AD_STAT_MARKER_RX] = decode_nla_u64,
+ [BOND_3AD_STAT_MARKER_TX] = decode_nla_u64,
+ [BOND_3AD_STAT_MARKER_RESP_RX] = decode_nla_u64,
+ [BOND_3AD_STAT_MARKER_RESP_TX] = decode_nla_u64,
+ [BOND_3AD_STAT_MARKER_UNKNOWN_RX] = decode_nla_u64,
+ [BOND_3AD_STAT_PAD] = NULL,
+};
+
+static bool
+decode_ifstats_link_xstats_bond_3ad(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ decode_nlattr(tcp, addr, len, ifstats_xstats_bond_3ad_attrs,
+ "BOND_XSTATS_???",
+ ARRSZ_PAIR(ifstats_xstats_bond_3ad_decoders),
+ opaque_data);
+
+ return true;
+}
+
+static const nla_decoder_t ifstats_xstats_bond_decoders[] = {
+ [BOND_XSTATS_UNSPEC] = NULL,
+ [BOND_XSTATS_3AD] = decode_ifstats_link_xstats_bond_3ad,
+};
+
+static bool
+decode_ifstats_link_xstats_bond(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ decode_nlattr(tcp, addr, len, ifstats_xstats_bond_attrs,
+ "BOND_XSTATS_???",
+ ARRSZ_PAIR(ifstats_xstats_bond_decoders),
+ opaque_data);
+
+ return true;
+}
+
+static const nla_decoder_t ifstats_xstats_decoders[] = {
+ [LINK_XSTATS_TYPE_UNSPEC] = NULL,
+ [LINK_XSTATS_TYPE_BRIDGE] = decode_ifstats_link_xstats_bridge,
+ [LINK_XSTATS_TYPE_BOND] = decode_ifstats_link_xstats_bond,
+};
+
+static bool
+decode_ifstats_link_xstats(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ decode_nlattr(tcp, addr, len, ifstats_xstats_type_attrs,
+ "LINK_XSTATS_TYPE_???",
+ ARRSZ_PAIR(ifstats_xstats_decoders),
+ opaque_data);
+
+ return true;
+}
+
+static const nla_decoder_t ifstats_offload_xstats_decoders[] = {
+ [IFLA_OFFLOAD_XSTATS_UNSPEC] = NULL,
+ [IFLA_OFFLOAD_XSTATS_CPU_HIT] = decode_nla_rtnl_link_stats64,
+};
+
+static bool
+decode_ifstats_link_offload_xstats(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ decode_nlattr(tcp, addr, len, ifstats_offload_attrs,
+ "IFLA_OFFLOAD_XSTATS_???",
+ ARRSZ_PAIR(ifstats_offload_xstats_decoders),
+ opaque_data);
+
+ return true;
+}
+
+static bool
+decode_ifstats_af_mpls_stats_link(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ struct mpls_link_stats st;
+
+ if (len < sizeof(st))
+ return false;
+
+ if (umove_or_printaddr(tcp, addr, &st))
+ return true;
+
+ tprint_struct_begin();
+ PRINT_FIELD_U(st, rx_packets);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, tx_packets);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, rx_bytes);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, tx_bytes);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, rx_errors);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, tx_errors);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, rx_dropped);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, tx_dropped);
+ tprint_struct_next();
+ PRINT_FIELD_U(st, rx_noroute);
+ tprint_struct_end();
+
+ if (len > sizeof(st)) {
+ tprint_array_next();
+ printstr_ex(tcp, addr + sizeof(st), len - sizeof(st),
+ QUOTE_FORCE_HEX);
+ }
+
+ return true;
+}
+
+static const nla_decoder_t ifla_stats_mpls_nla_decoders[] = {
+ [MPLS_STATS_UNSPEC] = NULL,
+ [MPLS_STATS_LINK] = decode_ifstats_af_mpls_stats_link,
+};
+
+static bool
+decode_ifstats_af(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ static const struct decoder_desc {
+ uint8_t af;
+ const struct xlat *xlat;
+ const char *dflt;
+ const nla_decoder_t *table;
+ size_t size;
+ } protos[] = {
+ { AF_MPLS, ifstats_af_spec_mpls_attrs, "MPLS_STATS_???",
+ ARRSZ_PAIR(ifla_stats_mpls_nla_decoders) },
+ };
+
+ uintptr_t proto = (uintptr_t) opaque_data;
+ const struct decoder_desc *desc = NULL;
+
+ for (size_t i = 0; i < ARRAY_SIZE(protos); i++) {
+ if (protos[i].af == proto) {
+ desc = protos + i;
+ break;
+ }
+ }
+
+ if (!desc)
+ return false;
+
+ decode_nlattr(tcp, addr, len,
+ desc->xlat, desc->dflt, desc->table, desc->size, NULL);
+
+ return true;
+}
+
+static bool
+decode_ifstats_af_spec(struct tcb *const tcp,
+ const kernel_ulong_t addr,
+ const unsigned int len,
+ const void *const opaque_data)
+{
+ static const nla_decoder_t af_spec_decoder = &decode_ifstats_af;
+
+ decode_nlattr(tcp, addr, len, addrfams, "AF_???",
+ &af_spec_decoder, 0, 0);
+
+ return true;
+}
+
+
+static const nla_decoder_t ifstatsmsg_nla_decoders[] = {
+ [IFLA_STATS_UNSPEC] = NULL,
+ [IFLA_STATS_LINK_64] = decode_nla_rtnl_link_stats64,
+ [IFLA_STATS_LINK_XSTATS] = decode_ifstats_link_xstats,
+ [IFLA_STATS_LINK_XSTATS_SLAVE] = decode_ifstats_link_xstats,
+ [IFLA_STATS_LINK_OFFLOAD_XSTATS] = decode_ifstats_link_offload_xstats,
+ [IFLA_STATS_AF_SPEC] = decode_ifstats_af_spec,
+};
+
+DECL_NETLINK_ROUTE_DECODER(decode_ifstatsmsg)
+{
+ struct if_stats_msg ifstats = { .family = family };
+ size_t offset = sizeof(ifstats.family);
+ bool decode_nla = false;
+
+ tprint_struct_begin();
+ PRINT_FIELD_XVAL(ifstats, family, addrfams, "AF_???");
+ tprint_struct_next();
+
+ if (len >= sizeof(ifstats)) {
+ if (!umoven_or_printaddr(tcp, addr + offset,
+ sizeof(ifstats) - offset,
+ (char *) &ifstats + offset)) {
+ if (ifstats.pad1) {
+ PRINT_FIELD_X(ifstats, pad1);
+ tprint_struct_next();
+ }
+ if (ifstats.pad2) {
+ PRINT_FIELD_X(ifstats, pad2);
+ tprint_struct_next();
+ }
+ PRINT_FIELD_IFINDEX(ifstats, ifindex);
+ tprint_struct_next();
+ PRINT_FIELD_FLAGS(ifstats, filter_mask,
+ ifstats_attr_flags,
+ "1<<IFLA_STATS_???");
+ decode_nla = true;
+ }
+ } else {
+ tprint_more_data_follows();
+ }
+ tprint_struct_end();
+
+ offset = NLMSG_ALIGN(sizeof(ifstats));
+ if (decode_nla && len > offset) {
+ tprint_array_next();
+ decode_nlattr(tcp, addr + offset, len - offset,
+ ifstats_attrs, "IFLA_STATS_???",
+ ARRSZ_PAIR(ifstatsmsg_nla_decoders), &ifstats);
+ }
+}
diff --git a/src/xlat/ifstats_af_spec_mpls_attrs.in b/src/xlat/ifstats_af_spec_mpls_attrs.in
new file mode 100644
index 000000000..4b43e0f2c
--- /dev/null
+++ b/src/xlat/ifstats_af_spec_mpls_attrs.in
@@ -0,0 +1,4 @@
+#unconditional
+#value_indexed
+MPLS_STATS_UNSPEC 0
+MPLS_STATS_LINK 1
diff --git a/src/xlat/ifstats_attr_flags.in b/src/xlat/ifstats_attr_flags.in
new file mode 100644
index 000000000..8c9650bbd
--- /dev/null
+++ b/src/xlat/ifstats_attr_flags.in
@@ -0,0 +1,7 @@
+#unconditional
+1<<IFLA_STATS_UNSPEC
+1<<IFLA_STATS_LINK_64
+1<<IFLA_STATS_LINK_XSTATS
+1<<IFLA_STATS_LINK_XSTATS_SLAVE
+1<<IFLA_STATS_LINK_OFFLOAD_XSTATS
+1<<IFLA_STATS_AF_SPEC
diff --git a/src/xlat/ifstats_attrs.in b/src/xlat/ifstats_attrs.in
new file mode 100644
index 000000000..c4d4c4e47
--- /dev/null
+++ b/src/xlat/ifstats_attrs.in
@@ -0,0 +1,8 @@
+#unconditional
+#value_indexed
+IFLA_STATS_UNSPEC 0
+IFLA_STATS_LINK_64 1
+IFLA_STATS_LINK_XSTATS 2
+IFLA_STATS_LINK_XSTATS_SLAVE 3
+IFLA_STATS_LINK_OFFLOAD_XSTATS 4
+IFLA_STATS_AF_SPEC 5
diff --git a/src/xlat/ifstats_offload_attrs.in b/src/xlat/ifstats_offload_attrs.in
new file mode 100644
index 000000000..34159078d
--- /dev/null
+++ b/src/xlat/ifstats_offload_attrs.in
@@ -0,0 +1,4 @@
+#unconditional
+#value_indexed
+IFLA_OFFLOAD_XSTATS_UNSPEC 0
+IFLA_OFFLOAD_XSTATS_CPU_HIT 1
diff --git a/src/xlat/ifstats_xstats_bond_3ad_attrs.in b/src/xlat/ifstats_xstats_bond_3ad_attrs.in
new file mode 100644
index 000000000..c857c6a7a
--- /dev/null
+++ b/src/xlat/ifstats_xstats_bond_3ad_attrs.in
@@ -0,0 +1,12 @@
+#unconditional
+#value_indexed
+BOND_3AD_STAT_LACPDU_RX 0
+BOND_3AD_STAT_LACPDU_TX 1
+BOND_3AD_STAT_LACPDU_UNKNOWN_RX 2
+BOND_3AD_STAT_LACPDU_ILLEGAL_RX 3
+BOND_3AD_STAT_MARKER_RX 4
+BOND_3AD_STAT_MARKER_TX 5
+BOND_3AD_STAT_MARKER_RESP_RX 6
+BOND_3AD_STAT_MARKER_RESP_TX 7
+BOND_3AD_STAT_MARKER_UNKNOWN_RX 8
+BOND_3AD_STAT_PAD 9
diff --git a/src/xlat/ifstats_xstats_bond_attrs.in b/src/xlat/ifstats_xstats_bond_attrs.in
new file mode 100644
index 000000000..db27e50a2
--- /dev/null
+++ b/src/xlat/ifstats_xstats_bond_attrs.in
@@ -0,0 +1,4 @@
+#unconditional
+#value_indexed
+BOND_XSTATS_UNSPEC 0
+BOND_XSTATS_3AD 1
diff --git a/src/xlat/ifstats_xstats_bridge_attrs.in b/src/xlat/ifstats_xstats_bridge_attrs.in
new file mode 100644
index 000000000..8f80bfe6a
--- /dev/null
+++ b/src/xlat/ifstats_xstats_bridge_attrs.in
@@ -0,0 +1,7 @@
+#unconditional
+#value_indexed
+BRIDGE_XSTATS_UNSPEC 0
+BRIDGE_XSTATS_VLAN 1
+BRIDGE_XSTATS_MCAST 2
+BRIDGE_XSTATS_PAD 3
+BRIDGE_XSTATS_STP 4
diff --git a/src/xlat/ifstats_xstats_bridge_mcast_indices.in b/src/xlat/ifstats_xstats_bridge_mcast_indices.in
new file mode 100644
index 000000000..97116d120
--- /dev/null
+++ b/src/xlat/ifstats_xstats_bridge_mcast_indices.in
@@ -0,0 +1,4 @@
+#unconditional
+#value_indexed
+BR_MCAST_DIR_RX 0
+BR_MCAST_DIR_TX 1
diff --git a/src/xlat/ifstats_xstats_type_attrs.in b/src/xlat/ifstats_xstats_type_attrs.in
new file mode 100644
index 000000000..2905dfe1d
--- /dev/null
+++ b/src/xlat/ifstats_xstats_type_attrs.in
@@ -0,0 +1,5 @@
+#unconditional
+#value_indexed
+LINK_XSTATS_TYPE_UNSPEC 0
+LINK_XSTATS_TYPE_BRIDGE 1
+LINK_XSTATS_TYPE_BOND 2
diff --git a/src/xlat/nl_bridge_vlan_flags.in b/src/xlat/nl_bridge_vlan_flags.in
new file mode 100644
index 000000000..041ef5a8d
--- /dev/null
+++ b/src/xlat/nl_bridge_vlan_flags.in
@@ -0,0 +1,8 @@
+#unconditional
+BRIDGE_VLAN_INFO_MASTER (1<<0)
+BRIDGE_VLAN_INFO_PVID (1<<1)
+BRIDGE_VLAN_INFO_UNTAGGED (1<<2)
+BRIDGE_VLAN_INFO_RANGE_BEGIN (1<<3)
+BRIDGE_VLAN_INFO_RANGE_END (1<<4)
+BRIDGE_VLAN_INFO_BRENTRY (1<<5)
+BRIDGE_VLAN_INFO_ONLY_OPTS (1<<6)
diff --git a/tests/.gitignore b/tests/.gitignore
index b957dfc5c..c2f498178 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -547,6 +547,10 @@ nlattr_ifla_xdp-y
nlattr_inet_diag_msg
nlattr_inet_diag_req_compat
nlattr_inet_diag_req_v2
+nlattr_ifstats
+nlattr_ifstats-Xabbrev
+nlattr_ifstats-Xraw
+nlattr_ifstats-Xverbose
nlattr_mdba_mdb_entry
nlattr_mdba_router_port
nlattr_ndmsg
diff --git a/tests/gen_tests.in b/tests/gen_tests.in
index 588fe24bf..9b4122301 100644
--- a/tests/gen_tests.in
+++ b/tests/gen_tests.in
@@ -522,6 +522,10 @@ nlattr_ifla_xdp-y +netlink_sock_diag.test -y; exec 9</dev/full
nlattr_inet_diag_msg +netlink_sock_diag.test
nlattr_inet_diag_req_compat +netlink_sock_diag.test
nlattr_inet_diag_req_v2 +netlink_sock_diag.test
+nlattr_ifstats +netlink_sock_diag.test
+nlattr_ifstats-Xabbrev +netlink_sock_diag.test -Xabbrev
+nlattr_ifstats-Xraw +netlink_sock_diag.test -Xraw
+nlattr_ifstats-Xverbose +netlink_sock_diag.test -Xverbose
nlattr_mdba_mdb_entry +netlink_sock_diag.test
nlattr_mdba_router_port +netlink_sock_diag.test
nlattr_ndmsg +netlink_sock_diag.test
diff --git a/tests/netlink_route.c b/tests/netlink_route.c
index 17f168ee1..96b22d48c 100644
--- a/tests/netlink_route.c
+++ b/tests/netlink_route.c
@@ -19,6 +19,7 @@
#include <linux/if_addrlabel.h>
#include <linux/if_arp.h>
#include <linux/if_bridge.h>
+#include <linux/if_link.h>
#include <linux/ip.h>
#include <linux/neighbour.h>
#include <linux/netconf.h>
@@ -640,6 +641,55 @@ test_rtnl_nexthop(const int fd)
test_rtnl_unknown_msg(fd, RTM_NEWNEXTHOP + 3);
}
+static void
+test_rtnl_ifstats(const int fd)
+{
+ static const struct strval32 types[] = {
+ { ARG_STR(RTM_NEWSTATS) },
+ { ARG_STR(RTM_GETSTATS) },
+ };
+ const struct {
+ struct if_stats_msg msg;
+ const char *af_str;
+ const char *rest_str;
+ } msgs[] = {
+ { { .family = AF_UNIX, .pad1 = 0, .pad2 = 0,
+ .ifindex = ifindex_lo(), .filter_mask = 0, },
+ "{family=AF_UNIX", ", ifindex=" IFINDEX_LO_STR
+ ", filter_mask=0}" },
+ { { .family = 44, .pad1 = 0, .pad2 = 0xdead,
+ .ifindex = 0xdeadbeef, .filter_mask = 1, },
+ "{family=AF_XDP", ", pad2=0xdead, ifindex=3735928559"
+ ", filter_mask=1<<IFLA_STATS_UNSPEC}" },
+ { { .family = 45, .pad1 = 0xca, .pad2 = 0,
+ .ifindex = ifindex_lo(), .filter_mask = 0xff, },
+ "{family=0x2d /* AF_??? */", ", pad1=0xca"
+ ", ifindex=" IFINDEX_LO_STR
+ ", filter_mask=1<<IFLA_STATS_UNSPEC|1<<IFLA_STATS_LINK_64"
+ "|1<<IFLA_STATS_LINK_XSTATS|1<<IFLA_STATS_LINK_XSTATS_SLAVE"
+ "|1<<IFLA_STATS_LINK_OFFLOAD_XSTATS|1<<IFLA_STATS_AF_SPEC"
+ "|0xc0}" },
+ { { .family = 255, .pad1 = 0xde, .pad2 = 0xbeef,
+ .ifindex = ifindex_lo(), .filter_mask = 0xdec0dec0, },
+ "{family=0xff /* AF_??? */", ", pad1=0xde"
+ ", pad2=0xbeef, ifindex=" IFINDEX_LO_STR
+ ", filter_mask=0xdec0dec0 /* 1<<IFLA_STATS_??? */}" },
+ };
+ void *const nlh0 = midtail_alloc(NLMSG_HDRLEN, sizeof(msgs[0].msg));
+
+ for (size_t i = 0; i < ARRAY_SIZE(types); i++) {
+ for (size_t j = 0; j < ARRAY_SIZE(msgs); j++) {
+ TEST_NL_ROUTE_(fd, nlh0, types[i].val, types[i].str,
+ msgs[j].msg,
+ printf("%s", msgs[j].af_str),
+ printf("%s", msgs[j].rest_str));
+ }
+ }
+
+ test_rtnl_unknown_msg(fd, RTM_NEWSTATS + 1);
+ test_rtnl_unknown_msg(fd, RTM_NEWSTATS + 3);
+}
+
int main(void)
{
skip_if_unavailable("/proc/self/fd/");
@@ -678,12 +728,7 @@ int main(void)
test_rtnl_netconf(fd); /* 80 */
test_rtnl_mdb(fd); /* 84 */
test_rtnl_nsid(fd); /* 88 */
-
- /* stats */ /* 92 */
- test_rtnl_unsupported_msg(fd, ARG_STR(RTM_NEWSTATS));
- test_rtnl_unknown_msg(fd, RTM_NEWSTATS + 1);
- test_rtnl_unsupported_msg(fd, ARG_STR(RTM_GETSTATS));
- test_rtnl_unknown_msg(fd, RTM_NEWSTATS + 3);
+ test_rtnl_ifstats(fd); /* 92 */
/* cachereport */ /* 96 */
test_rtnl_unsupported_msg(fd, ARG_STR(RTM_NEWCACHEREPORT));
diff --git a/tests/nlattr_ifstats-Xabbrev.c b/tests/nlattr_ifstats-Xabbrev.c
new file mode 100644
index 000000000..d9da303d8
--- /dev/null
+++ b/tests/nlattr_ifstats-Xabbrev.c
@@ -0,0 +1,2 @@
+#define XLAT_ABBREV 1
+#include "nlattr_ifstats.c"
diff --git a/tests/nlattr_ifstats-Xraw.c b/tests/nlattr_ifstats-Xraw.c
new file mode 100644
index 000000000..0e5c5ab07
--- /dev/null
+++ b/tests/nlattr_ifstats-Xraw.c
@@ -0,0 +1,2 @@
+#define XLAT_RAW 1
+#include "nlattr_ifstats.c"
diff --git a/tests/nlattr_ifstats-Xverbose.c b/tests/nlattr_ifstats-Xverbose.c
new file mode 100644
index 000000000..a2129cd26
--- /dev/null
+++ b/tests/nlattr_ifstats-Xverbose.c
@@ -0,0 +1,2 @@
+#define XLAT_VERBOSE 1
+#include "nlattr_ifstats.c"
diff --git a/tests/nlattr_ifstats.c b/tests/nlattr_ifstats.c
new file mode 100644
index 000000000..c42374e76
--- /dev/null
+++ b/tests/nlattr_ifstats.c
@@ -0,0 +1,784 @@
+/*
+ * Copyright (c) 2019-2021 Eugene Syromyatnikov <evgsyr@gmail.com>
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "tests.h"
+
+#include <arpa/inet.h>
+#include <inttypes.h>
+#include <linux/ip.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "test_nlattr.h"
+
+#include <linux/if_link.h>
+#include <linux/if_bonding.h>
+#include <linux/if_bridge.h>
+#include <linux/mpls.h>
+
+#include "xlat.h"
+#include "xlat/addrfams.h"
+#define XLAT_MACROS_ONLY
+# include "xlat/ifstats_af_spec_mpls_attrs.h"
+# include "xlat/ifstats_attrs.h"
+# include "xlat/ifstats_attr_flags.h"
+# include "xlat/ifstats_offload_attrs.h"
+# include "xlat/ifstats_xstats_bond_attrs.h"
+# include "xlat/ifstats_xstats_bond_3ad_attrs.h"
+# include "xlat/ifstats_xstats_bridge_attrs.h"
+# include "xlat/ifstats_xstats_bridge_mcast_indices.h"
+# include "xlat/ifstats_xstats_type_attrs.h"
+# include "xlat/nl_bridge_vlan_flags.h"
+#undef XLAT_MACROS_ONLY
+
+static const unsigned int hdrlen = sizeof(struct if_stats_msg);
+static char pattern[4096];
+static char nla_type_str[256];
+
+static void
+init_ifstats(struct nlmsghdr *const nlh, const unsigned int msg_len)
+{
+ SET_STRUCT(struct nlmsghdr, nlh,
+ .nlmsg_len = msg_len,
+ .nlmsg_type = RTM_GETSTATS,
+ .nlmsg_flags = NLM_F_DUMP,
+ );
+
+ struct if_stats_msg *const msg = NLMSG_DATA(nlh);
+ SET_STRUCT(struct if_stats_msg, msg,
+ .family = AF_UNIX,
+ .ifindex = ifindex_lo(),
+ .filter_mask = 0x22,
+ );
+}
+
+static void
+print_ifstats(const unsigned int msg_len)
+{
+ printf("{nlmsg_len=%u, nlmsg_type=" XLAT_FMT ", nlmsg_flags=" XLAT_FMT
+ ", nlmsg_seq=0, nlmsg_pid=0}, {family=" XLAT_FMT ", ifindex="
+ XLAT_FMT_U ", filter_mask=" XLAT_FMT "}",
+ msg_len, XLAT_ARGS(RTM_GETSTATS), XLAT_ARGS(NLM_F_DUMP),
+ XLAT_ARGS(AF_UNIX), XLAT_SEL(ifindex_lo(), IFINDEX_LO_STR),
+ XLAT_ARGS(1<<IFLA_STATS_LINK_64|1<<IFLA_STATS_AF_SPEC));
+}
+
+/*
+ * NB: these functions use global variables to control which top-level
+ * netlink attribute is to be used.
+ */
+#define DEF_NLATTR_FUNCS_NESTED(sfx_, attr_var_, attr_str_var_, \
+ parent_sfx_, lvl_) \
+ static void \
+ init_ifstats_##sfx_(struct nlmsghdr *const nlh, \
+ const unsigned int msg_len) \
+ { \
+ (init_##parent_sfx_)(nlh, msg_len); \
+ struct nlattr *nla = NLMSG_ATTR(nlh, \
+ sizeof(struct if_stats_msg) \
+ + ((lvl_) - 1) * NLA_HDRLEN); \
+ SET_STRUCT(struct nlattr, nla, \
+ .nla_len = msg_len \
+ - NLMSG_SPACE(sizeof(struct if_stats_msg)) \
+ - ((lvl_) - 1) * NLA_HDRLEN, \
+ .nla_type = (attr_var_), \
+ ); \
+ } \
+ \
+ static void \
+ print_ifstats_##sfx_(const unsigned int msg_len) \
+ { \
+ (print_##parent_sfx_)(msg_len); \
+ printf(", [{nla_len=%u, nla_type=%s}", \
+ (unsigned int) (msg_len - NLMSG_HDRLEN \
+ - NLMSG_ALIGN(sizeof(struct \
+ if_stats_msg)) \
+ - ((lvl_) - 1) * NLA_HDRLEN), \
+ attr_str_var_); \
+ } \
+ /* End of DEF_NLATTR_FUNCS_NESTED */
+
+static uint16_t l1_attr;
+static char l1_attr_str[256];
+
+static uint16_t l2_attr;
+static char l2_attr_str[256];
+
+static uint16_t l3_attr;
+static char l3_attr_str[256];
+
+DEF_NLATTR_FUNCS_NESTED(l1, l1_attr, l1_attr_str, ifstats, 1)
+DEF_NLATTR_FUNCS_NESTED(l2, l2_attr, l2_attr_str, ifstats_l1, 2)
+DEF_NLATTR_FUNCS_NESTED(l3, l3_attr, l3_attr_str, ifstats_l2, 3)
+
+
+static void
+print_stats_64(struct rtnl_link_stats64 *st, size_t sz)
+{
+ printf("{"); PRINT_FIELD_U(*st, rx_packets);
+ printf(", "); PRINT_FIELD_U(*st, tx_packets);
+ printf(", "); PRINT_FIELD_U(*st, rx_bytes);
+ printf(", "); PRINT_FIELD_U(*st, tx_bytes);
+ printf(", "); PRINT_FIELD_U(*st, rx_errors);
+ printf(", "); PRINT_FIELD_U(*st, tx_errors);
+ printf(", "); PRINT_FIELD_U(*st, rx_dropped);
+ printf(", "); PRINT_FIELD_U(*st, tx_dropped);
+ printf(", "); PRINT_FIELD_U(*st, multicast);
+ printf(", "); PRINT_FIELD_U(*st, collisions);
+ printf(", "); PRINT_FIELD_U(*st, rx_length_errors);
+ printf(", "); PRINT_FIELD_U(*st, rx_over_errors);
+ printf(", "); PRINT_FIELD_U(*st, rx_crc_errors);
+ printf(", "); PRINT_FIELD_U(*st, rx_frame_errors);
+ printf(", "); PRINT_FIELD_U(*st, rx_fifo_errors);
+ printf(", "); PRINT_FIELD_U(*st, rx_missed_errors);
+ printf(", "); PRINT_FIELD_U(*st, tx_aborted_errors);
+ printf(", "); PRINT_FIELD_U(*st, tx_carrier_errors);
+ printf(", "); PRINT_FIELD_U(*st, tx_fifo_errors);
+ printf(", "); PRINT_FIELD_U(*st, tx_heartbeat_errors);
+ printf(", "); PRINT_FIELD_U(*st, tx_window_errors);
+ printf(", "); PRINT_FIELD_U(*st, rx_compressed);
+ printf(", "); PRINT_FIELD_U(*st, tx_compressed);
+ if (sz > offsetofend(struct rtnl_link_stats64, tx_compressed)) {
+ printf(", ");
+ PRINT_FIELD_U(*st, rx_nohandler);
+ }
+ printf("}");
+}
+
+static void
+check_stats_64(const int fd, unsigned int cmd, const char *cmd_str, bool nest)
+{
+ static const size_t minsz = offsetofend(struct rtnl_link_stats64,
+ tx_compressed);
+
+ struct rtnl_link_stats64 st;
+ void *nlh0 = midtail_alloc(NLMSG_SPACE(hdrlen),
+ (!!nest + 1) * NLA_HDRLEN + sizeof(st));
+
+ snprintf(nla_type_str, sizeof(nla_type_str), XLAT_FMT,
+ XLAT_SEL(cmd, cmd_str));
+ fill_memory(&st, sizeof(st));
+
+ TEST_NESTED_NLATTR_OBJECT_EX_MINSZ_(fd, nlh0, hdrlen,
+ nest ? init_ifstats_l1
+ : init_ifstats,
+ nest ? print_ifstats_l1
+ : print_ifstats,
+ cmd, nla_type_str,
+ pattern, st, minsz,
+ print_quoted_hex, (unsigned) !!nest,
+ print_stats_64(&st, sizeof(st)));
+
+ TEST_NLATTR_(fd, nlh0 - !!nest * NLA_HDRLEN,
+ hdrlen + !!nest * NLA_HDRLEN,
+ nest ? init_ifstats_l1 : init_ifstats,
+ nest ? print_ifstats_l1 : print_ifstats,
+ cmd, nla_type_str, minsz, &st, minsz,
+ print_stats_64(&st, minsz);
+ for (size_t i = 0; i < (unsigned) !!nest; i++)
+ printf("]"));
+}
+
+static void
+fmt_str(char *dst, size_t dst_sz, uint32_t cmd, const char *s, const char *dflt)
+{
+ if (s) {
+ snprintf(dst, dst_sz, XLAT_FMT, XLAT_SEL(cmd, s));
+ } else {
+ snprintf(dst, dst_sz, "%#x" NRAW(" /* ") "%s" NRAW(" */"),
+ cmd, NRAW(dflt));
+ }
+}
+
+static void
+print_mcast_stats(struct br_mcast_stats *br_xst_mc)
+{
+#define PR_FIELD_(pfx_, field_) \
+ printf(pfx_ #field_ "=[[" XLAT_KNOWN(0, "BR_MCAST_DIR_RX") \
+ "] = %llu, [" XLAT_KNOWN(1, "BR_MCAST_DIR_TX") "] = %llu]", \
+ (unsigned long long) br_xst_mc->field_[0], \
+ (unsigned long long) br_xst_mc->field_[1])
+
+ PR_FIELD_("{", igmp_v1queries);
+ PR_FIELD_(", ", igmp_v2queries);
+ PR_FIELD_(", ", igmp_v3queries);
+ PR_FIELD_(", ", igmp_leaves);
+ PR_FIELD_(", ", igmp_v1reports);
+ PR_FIELD_(", ", igmp_v2reports);
+ PR_FIELD_(", ", igmp_v3reports);
+ printf(", igmp_parse_errors=%llu",
+ (unsigned long long) br_xst_mc->igmp_parse_errors);
+ PR_FIELD_(", ", mld_v1queries);
+ PR_FIELD_(", ", mld_v2queries);
+ PR_FIELD_(", ", mld_leaves);
+ PR_FIELD_(", ", mld_v1reports);
+ PR_FIELD_(", ", mld_v2reports);
+ printf(", mld_parse_errors=%llu",
+ (unsigned long long) br_xst_mc->mld_parse_errors);
+ PR_FIELD_(", ", mcast_bytes);
+ PR_FIELD_(", ", mcast_packets);
+ printf("}");
+
+#undef PR_FIELD_
+}
+
+static void
+check_xstats(const int fd, unsigned int cmd, const char *cmd_str)
+{
+ void *nlh0 = midtail_alloc(NLMSG_SPACE(hdrlen) + 3 * NLA_HDRLEN,
+ NLA_HDRLEN + 256);
+
+ l1_attr = cmd;
+ snprintf(l1_attr_str, sizeof(l1_attr_str), XLAT_FMT,
+ XLAT_SEL(cmd, cmd_str));
+
+ /* Unknown, unimplemented, no semantics. */
+ static const struct strval32 undec_types[] = {
+ { ARG_STR(LINK_XSTATS_TYPE_UNSPEC) },
+ { 0x3 },
+ { 0xbad },
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(undec_types); i++) {
+ fmt_str(nla_type_str, sizeof(nla_type_str),
+ undec_types[i].val, undec_types[i].str,
+ "LINK_XSTATS_TYPE_???");
+ TEST_NLATTR_(fd, nlh0, hdrlen + NLA_HDRLEN,
+ init_ifstats_l1, print_ifstats_l1,
+ undec_types[i].val, nla_type_str,
+ 37, pattern, 37,
+ print_quoted_hex(pattern, 32);
+ printf("...]"));
+ }
+
+ /* LINK_XSTATS_TYPE_BRIDGE */
+ l2_attr = LINK_XSTATS_TYPE_BRIDGE;
+ snprintf(l2_attr_str, sizeof(l2_attr_str), XLAT_FMT,
+ XLAT_ARGS(LINK_XSTATS_TYPE_BRIDGE));
+
+ /* LINK_XSTATS_TYPE_BRIDGE: Unknown, unimplemented, no semantics. */
+ static const struct strval32 undec_br_types[] = {
+ { ARG_STR(BRIDGE_XSTATS_UNSPEC) },
+ { ARG_STR(BRIDGE_XSTATS_PAD) },
+ { 0x5 },
+ { 0xbad },
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(undec_br_types); i++) {
+ fmt_str(nla_type_str, sizeof(nla_type_str),
+ undec_br_types[i].val, undec_br_types[i].str,
+ "BRIDGE_XSTATS_???");
+ TEST_NLATTR_(fd, nlh0, hdrlen + 2 * NLA_HDRLEN,
+ init_ifstats_l2, print_ifstats_l2,
+ undec_br_types[i].val, nla_type_str,
+ 37, pattern, 37,
+ print_quoted_hex(pattern, 32);
+ printf("...]]"));
+ }
+
+ /* LINK_XSTATS_TYPE_BRIDGE: BRIDGE_XSTATS_VLAN */
+ static const struct {
+ struct bridge_vlan_xstats val;
+ const char *str;
+ } br_xst_vlan_vecs[] = {
+ { { .rx_bytes=0, .rx_packets=0xdeadfacebeeffeedULL,
+ .tx_bytes=0x8090a0b0c0d0e0f0ULL, .tx_packets=0,
+ .vid=0xdead, .flags=2 },
+ "{rx_bytes=0, rx_packets=16045756813264551661"
+ ", tx_bytes=9264081114510713072, tx_packets=0"
+ ", vid=57005, flags="
+ XLAT_KNOWN(0x2, "BRIDGE_VLAN_INFO_PVID") "}" },
+ { { .rx_bytes=12345678901234567890ULL, .rx_packets=0,
+ .tx_bytes=0, .tx_packets=9876543210987654321ULL,
+ .vid=0, .flags=0, .pad2=0xbadc0ded },
+ "{rx_bytes=12345678901234567890, rx_packets=0"
+ ", tx_bytes=0, tx_packets=9876543210987654321"
+ ", vid=0, flags=0, pad2=0xbadc0ded}" },
+ { { .flags=0xdeed },
+ "{rx_bytes=0, rx_packets=0, tx_bytes=0, tx_packets=0, vid=0"
+ ", flags=" XLAT_KNOWN(0xdeed, "BRIDGE_VLAN_INFO_MASTER"
+ "|BRIDGE_VLAN_INFO_UNTAGGED"
+ "|BRIDGE_VLAN_INFO_RANGE_BEGIN"
+ "|BRIDGE_VLAN_INFO_BRENTRY"
+ "|BRIDGE_VLAN_INFO_ONLY_OPTS"
+ "|0xde80") "}" },
+ { { .rx_bytes=0xdefaceddecaffeedULL,
+ .rx_packets=0xbeeffacedeadbabeULL,
+ .tx_bytes=0xbeeffeeddadfacedULL,
+ .tx_packets=0xbeeffadeeffaceedULL,
+ .vid=0xcafe, .flags=0xfa80, .pad2=0xdeadabba },
+ "{rx_bytes=16067382073151717101"
+ ", rx_packets=13758491153046289086"
+ ", tx_bytes=13758495684172950765"
+ ", tx_packets=13758491222056029933"
+ ", vid=51966, flags=0xfa80"
+ NRAW(" /* BRIDGE_VLAN_INFO_??? */")
+ ", pad2=0xdeadabba}" },
+ };
+ void *nlh_vlan = midtail_alloc(NLMSG_SPACE(hdrlen),
+ 2 * NLA_HDRLEN
+ + sizeof(struct bridge_vlan_xstats));
+
+ for (size_t i = 0; i < ARRAY_SIZE(br_xst_vlan_vecs); i++) {
+ TEST_NESTED_NLATTR_OBJECT_EX_(fd, nlh_vlan, hdrlen,
+ init_ifstats_l2, print_ifstats_l2,
+ BRIDGE_XSTATS_VLAN,
+ XLAT_KNOWN(0x1,
+ "BRIDGE_XSTATS_VLAN"),
+ pattern, br_xst_vlan_vecs[i].val,
+ print_quoted_hex, 2,
+ printf("%s",
+ br_xst_vlan_vecs[i].str));
+
+ char buf[sizeof(br_xst_vlan_vecs[0].val) + 42];
+ fill_memory(buf, sizeof(buf));
+ memcpy(buf, &br_xst_vlan_vecs[i].val,
+ sizeof(br_xst_vlan_vecs[i].val));
+ TEST_NLATTR_(fd, nlh0, hdrlen + 2 * NLA_HDRLEN,
+ init_ifstats_l2, print_ifstats_l2,
+ BRIDGE_XSTATS_VLAN,
+ XLAT_KNOWN(0x1, "BRIDGE_XSTATS_VLAN"),
+ sizeof(buf), buf, sizeof(buf),
+ printf("%s", br_xst_vlan_vecs[i].str);
+ printf(", ");
+ print_quoted_hex(buf
+ + sizeof(br_xst_vlan_vecs[0].val),
+ 32);
+ printf("...]]"));
+ }
+
+ /* LINK_XSTATS_TYPE_BRIDGE: BRIDGE_XSTATS_MCAST */
+ struct br_mcast_stats br_xst_mc;
+ void *nlh_mc = midtail_alloc(NLMSG_SPACE(hdrlen),
+ 2 * NLA_HDRLEN + sizeof(br_xst_mc));
+
+#define FIELD_STR_(field_) \
+ #field_ "=[[" XLAT_KNOWN(0, "BR_MCAST_DIR_RX") "] = 0, [" \
+ XLAT_KNOWN(1, "BR_MCAST_DIR_TX") "] = 0]"
+
+ memset(&br_xst_mc, 0, sizeof(br_xst_mc));
+ TEST_NESTED_NLATTR_OBJECT_EX_(fd, nlh_mc, hdrlen,
+ init_ifstats_l2, print_ifstats_l2,
+ BRIDGE_XSTATS_MCAST,
+ XLAT_KNOWN(0x2, "BRIDGE_XSTATS_MCAST"),
+ pattern, br_xst_mc,
+ print_quoted_hex, 2,
+ printf("{" FIELD_STR_(igmp_v1queries)
+ ", " FIELD_STR_(igmp_v2queries)
+ ", " FIELD_STR_(igmp_v3queries)
+ ", " FIELD_STR_(igmp_leaves)
+ ", " FIELD_STR_(igmp_v1reports)
+ ", " FIELD_STR_(igmp_v2reports)
+ ", " FIELD_STR_(igmp_v3reports)
+ ", igmp_parse_errors=0"
+ ", " FIELD_STR_(mld_v1queries)
+ ", " FIELD_STR_(mld_v2queries)
+ ", " FIELD_STR_(mld_leaves)
+ ", " FIELD_STR_(mld_v1reports)
+ ", " FIELD_STR_(mld_v2reports)
+ ", mld_parse_errors=0"
+ ", " FIELD_STR_(mcast_bytes)
+ ", " FIELD_STR_(mcast_packets)
+ "}"));
+#undef FIELD_STR_
+
+ fill_memory64(&br_xst_mc, sizeof(br_xst_mc));
+ TEST_NESTED_NLATTR_OBJECT_EX_(fd, nlh_mc, hdrlen,
+ init_ifstats_l2, print_ifstats_l2,
+ BRIDGE_XSTATS_MCAST,
+ XLAT_KNOWN(0x2, "BRIDGE_XSTATS_MCAST"),
+ pattern, br_xst_mc,
+ print_quoted_hex, 2,
+ print_mcast_stats(&br_xst_mc));
+
+ char mc_buf[sizeof(br_xst_mc) + 8];
+ fill_memory(mc_buf, sizeof(mc_buf));
+ memcpy(mc_buf, &br_xst_mc, sizeof(br_xst_mc));
+ TEST_NLATTR_(fd, nlh0, hdrlen + 2 * NLA_HDRLEN,
+ init_ifstats_l2, print_ifstats_l2,
+ BRIDGE_XSTATS_MCAST,
+ XLAT_KNOWN(0x2, "BRIDGE_XSTATS_MCAST"),
+ sizeof(mc_buf), mc_buf, sizeof(mc_buf),
+ print_mcast_stats(&br_xst_mc);
+ printf(", ");
+ print_quoted_hex(mc_buf + sizeof(br_xst_mc), 8);
+ printf("]]"));
+
+ /* LINK_XSTATS_TYPE_BRIDGE: BRIDGE_XSTATS_STP */
+ static const struct {
+ struct bridge_stp_xstats val;
+ const char *str;
+ } br_xst_stp_vecs[] = {
+ { { .transition_blk=0, .transition_fwd=0, .rx_bpdu=0,
+ .tx_bpdu=0, .rx_tcn=0, .tx_tcn=0, },
+ "{transition_blk=0, transition_fwd=0, rx_bpdu=0, tx_bpdu=0"
+ ", rx_tcn=0, tx_tcn=0}" },
+ { { .transition_blk=0x8090a0b0c0d0e0f0ULL,
+ .transition_fwd=0x8191a1b1c1d1e1f1ULL,
+ .rx_bpdu=0x8292a2b2c2d2e2f2ULL,
+ .tx_bpdu=0x8393a3b3c3d3e3f3ULL,
+ .rx_tcn=0x8494a4b4c4d4e4f4ULL,
+ .tx_tcn=0x8595a5b5c5d5e5f5ULL, },
+ "{transition_blk=9264081114510713072"
+ ", transition_fwd=9336421287348789745"
+ ", rx_bpdu=9408761460186866418, tx_bpdu=9481101633024943091"
+ ", rx_tcn=9553441805863019764, tx_tcn=9625781978701096437}" },
+ };
+ void *nlh_stp = midtail_alloc(NLMSG_SPACE(hdrlen),
+ 2 * NLA_HDRLEN
+ + sizeof(struct bridge_stp_xstats));
+
+ for (size_t i = 0; i < ARRAY_SIZE(br_xst_stp_vecs); i++) {
+ TEST_NESTED_NLATTR_OBJECT_EX_(fd, nlh_stp, hdrlen,
+ init_ifstats_l2, print_ifstats_l2,
+ BRIDGE_XSTATS_STP,
+ XLAT_KNOWN(0x4,
+ "BRIDGE_XSTATS_STP"),
+ pattern, br_xst_stp_vecs[i].val,
+ print_quoted_hex, 2,
+ printf("%s",
+ br_xst_stp_vecs[i].str));
+
+ char buf[sizeof(br_xst_stp_vecs[0].val) + 33];
+ fill_memory(buf, sizeof(buf));
+ memcpy(buf, &br_xst_stp_vecs[i].val,
+ sizeof(br_xst_stp_vecs[i].val));
+ TEST_NLATTR_(fd, nlh0, hdrlen + 2 * NLA_HDRLEN,
+ init_ifstats_l2, print_ifstats_l2,
+ BRIDGE_XSTATS_STP,
+ XLAT_KNOWN(0x4, "BRIDGE_XSTATS_STP"),
+ sizeof(buf), buf, sizeof(buf),
+ printf("%s", br_xst_stp_vecs[i].str);
+ printf(", ");
+ print_quoted_hex(buf
+ + sizeof(br_xst_stp_vecs[0].val),
+ 32);
+ printf("...]]"));
+ }
+
+
+
+ /* LINK_XSTATS_TYPE_BOND */
+ l2_attr = LINK_XSTATS_TYPE_BOND;
+ snprintf(l2_attr_str, sizeof(l2_attr_str), XLAT_FMT,
+ XLAT_ARGS(LINK_XSTATS_TYPE_BOND));
+
+ /* LINK_XSTATS_TYPE_BOND: Unknown, unimplemented, no semantics. */
+ static const struct strval32 undec_bd_types[] = {
+ { ARG_STR(BOND_XSTATS_UNSPEC) },
+ { 0x2 },
+ { 0xbad },
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(undec_bd_types); i++) {
+ fmt_str(nla_type_str, sizeof(nla_type_str),
+ undec_bd_types[i].val, undec_bd_types[i].str,
+ "BOND_XSTATS_???");
+ TEST_NLATTR_(fd, nlh0, hdrlen + 2 * NLA_HDRLEN,
+ init_ifstats_l2, print_ifstats_l2,
+ undec_bd_types[i].val, nla_type_str,
+ 37, pattern, 37,
+ print_quoted_hex(pattern, 32);
+ printf("...]]"));
+ }
+
+ /* LINK_XSTATS_TYPE_BOND: BOND_XSTATS_3AD */
+ l3_attr = BOND_XSTATS_3AD;
+ snprintf(l3_attr_str, sizeof(l3_attr_str), XLAT_FMT,
+ XLAT_ARGS(BOND_XSTATS_3AD));
+
+ /* BOND_XSTATS_3AD: Unknown, unimplemented, no semantics. */
+ static const struct strval32 undec_3ad_types[] = {
+ { ARG_STR(BOND_3AD_STAT_PAD) },
+ { 0xa },
+ { 0xbad },
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(undec_3ad_types); i++) {
+ fmt_str(nla_type_str, sizeof(nla_type_str),
+ undec_3ad_types[i].val, undec_3ad_types[i].str,
+ "BOND_XSTATS_???");
+ TEST_NLATTR_(fd, nlh0, hdrlen + 3 * NLA_HDRLEN,
+ init_ifstats_l3, print_ifstats_l3,
+ undec_3ad_types[i].val, nla_type_str,
+ 37, pattern, 37,
+ print_quoted_hex(pattern, 32);
+ printf("...]]]"));
+ }
+
+ /* BOND_XSTATS_3AD: u64 args */
+ static const struct strval32 u64_3ad_types[] = {
+ { ARG_STR(BOND_3AD_STAT_LACPDU_RX) },
+ { ARG_STR(BOND_3AD_STAT_LACPDU_TX) },
+ { ARG_STR(BOND_3AD_STAT_LACPDU_UNKNOWN_RX) },
+ { ARG_STR(BOND_3AD_STAT_LACPDU_ILLEGAL_RX) },
+ { ARG_STR(BOND_3AD_STAT_MARKER_RX) },
+ { ARG_STR(BOND_3AD_STAT_MARKER_TX) },
+ { ARG_STR(BOND_3AD_STAT_MARKER_RESP_RX) },
+ { ARG_STR(BOND_3AD_STAT_MARKER_RESP_TX) },
+ { ARG_STR(BOND_3AD_STAT_MARKER_UNKNOWN_RX) },
+ };
+ void *nlh_3ad_u64 = midtail_alloc(NLMSG_SPACE(hdrlen),
+ 3 * NLA_HDRLEN + sizeof(uint64_t));
+ for (size_t i = 0; i < ARRAY_SIZE(u64_3ad_types); i++) {
+ snprintf(nla_type_str, sizeof(nla_type_str), XLAT_FMT,
+ XLAT_SEL(u64_3ad_types[i].val, u64_3ad_types[i].str));
+ check_u64_nlattr(fd, nlh_3ad_u64, hdrlen,
+ init_ifstats_l3, print_ifstats_l3,
+ u64_3ad_types[i].val, nla_type_str,
+ pattern, 3);
+ }
+}
+
+static void
+check_stats_offload(const int fd)
+{
+ void *nlh0 = midtail_alloc(NLMSG_SPACE(hdrlen) + 3 * NLA_HDRLEN,
+ NLA_HDRLEN + 128);
+
+ /* IFLA_STATS_LINK_OFFLOAD_XSTATS */
+ l1_attr = IFLA_STATS_LINK_OFFLOAD_XSTATS;
+ snprintf(l1_attr_str, sizeof(l1_attr_str), XLAT_FMT,
+ XLAT_ARGS(IFLA_STATS_LINK_OFFLOAD_XSTATS));
+
+ /* Unknown, unimplemented, no semantics. */
+ static const struct strval32 undec_types[] = {
+ { ARG_STR(IFLA_OFFLOAD_XSTATS_UNSPEC) },
+ { 0x2 },
+ { 0xbad },
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(undec_types); i++) {
+ fmt_str(nla_type_str, sizeof(nla_type_str),
+ undec_types[i].val, undec_types[i].str,
+ "IFLA_OFFLOAD_XSTATS_???");
+ TEST_NLATTR_(fd, nlh0, hdrlen + NLA_HDRLEN,
+ init_ifstats_l1, print_ifstats_l1,
+ undec_types[i].val, nla_type_str,
+ 37, pattern, 37,
+ print_quoted_hex(pattern, 32);
+ printf("...]"));
+ }
+
+ /* IFLA_OFFLOAD_XSTATS_CPU_HIT */
+ check_stats_64(fd, ARG_STR(IFLA_OFFLOAD_XSTATS_CPU_HIT), true);
+}
+
+/*
+ * skip_af is expected to be sorted
+ *
+ * [RTM_GETSTATS] -> struct if_stats_msg
+ * [cmd]
+ * [AF_*]
+ * [0] -> u32
+ * [1] -> u64
+ */
+static void
+check_stats_af_generic(const int fd, unsigned int cmd, const char *cmd_str,
+ const uint8_t * const skip_af, const size_t skip_af_cnt)
+{
+ enum { ATTR_SZ = NLA_HDRLEN + 2 * NLA_HDRLEN + 4 + 8 };
+
+ /*
+ * The payload is designed like this so if a decoder for a new AF_*
+ * is implemented, this check will likely fail.
+ */
+ struct {
+ struct nlattr hdr;
+
+ struct nlattr nested_hdr1;
+ uint32_t nested_data1;
+
+ struct nlattr nested_hdr2;
+ uint64_t nested_data2;
+ } dummy_data = {
+ { ATTR_SZ, 0 /* AF_* */ },
+ { NLA_HDRLEN + sizeof(uint32_t), 0 }, 0xdeadc0de,
+ { NLA_HDRLEN + sizeof(uint64_t), 1 }, 0xbadda7adeadfacedULL,
+ };
+ void *nlh0 = midtail_alloc(NLMSG_SPACE(hdrlen), NLA_HDRLEN + ATTR_SZ);
+ size_t skip_pos = 0;
+
+ static_assert(ATTR_SZ == sizeof(dummy_data),
+ "Dummy nlattr payload size mismatch");
+
+ snprintf(nla_type_str, sizeof(nla_type_str), XLAT_FMT,
+ XLAT_SEL(cmd, cmd_str));
+
+ for (size_t i = 0; i < 256; i++) {
+ if (skip_pos < skip_af_cnt && i == skip_af[skip_pos]) {
+ skip_pos++;
+ continue;
+ }
+
+ dummy_data.hdr.nla_type = i;
+ TEST_NLATTR_(fd, nlh0, hdrlen, init_ifstats, print_ifstats,
+ cmd, nla_type_str, ATTR_SZ, &dummy_data, ATTR_SZ,
+ printf("[{nla_len=%u, nla_type=", ATTR_SZ);
+ printxval(addrfams, i, "AF_???");
+ printf("}, ");
+ print_quoted_hex(&dummy_data.nested_hdr1,
+ sizeof(dummy_data) - NLA_HDRLEN);
+ printf("]"));
+ }
+}
+
+static void
+check_stats_af_mpls(const int fd)
+{
+ void *nlh0 = midtail_alloc(NLMSG_SPACE(hdrlen) + 3 * NLA_HDRLEN,
+ NLA_HDRLEN + 128);
+
+ /* l1: IFLA_STATS_AF_SPEC */
+ l1_attr = IFLA_STATS_AF_SPEC;
+ snprintf(l1_attr_str, sizeof(l1_attr_str),
+ XLAT_KNOWN(0x5, "IFLA_STATS_AF_SPEC"));
+
+ /* l2: AF_MPLS */
+ l2_attr = AF_MPLS;
+ snprintf(l2_attr_str, sizeof(l2_attr_str), XLAT_FMT,
+ XLAT_ARGS(AF_MPLS));
+
+ /* Unknown, unimplemented, no semantics. */
+ static const struct strval32 undec_types[] = {
+ { ARG_STR(MPLS_STATS_UNSPEC) },
+ { 0x2 },
+ { 0xbad },
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(undec_types); i++) {
+ fmt_str(nla_type_str, sizeof(nla_type_str),
+ undec_types[i].val, undec_types[i].str,
+ "MPLS_STATS_???");
+ TEST_NLATTR_(fd, nlh0, hdrlen + 2 * NLA_HDRLEN,
+ init_ifstats_l2, print_ifstats_l2,
+ undec_types[i].val, nla_type_str,
+ 37, pattern, 37,
+ print_quoted_hex(pattern, 32);
+ printf("...]]"));
+ }
+
+ /* MPLS_STATS_LINK */
+ struct mpls_link_stats mls;
+ void *nlh_mls = midtail_alloc(NLMSG_SPACE(hdrlen),
+ 2 * NLA_HDRLEN + sizeof(mls));
+
+ memset(&mls, 0, sizeof(mls));
+ TEST_NESTED_NLATTR_OBJECT_EX_(fd, nlh_mls, hdrlen,
+ init_ifstats_l2, print_ifstats_l2,
+ MPLS_STATS_LINK,
+ XLAT_KNOWN(0x1, "MPLS_STATS_LINK"),
+ pattern, mls, print_quoted_hex, 2,
+ printf("{rx_packets=0, tx_packets=0"
+ ", rx_bytes=0, tx_bytes=0"
+ ", rx_errors=0, tx_errors=0"
+ ", rx_dropped=0, tx_dropped=0"
+ ", rx_noroute=0}"));
+
+ typedef unsigned long long ullong;
+ fill_memory64(&mls, sizeof(mls));
+ TEST_NESTED_NLATTR_OBJECT_EX_(fd, nlh_mls, hdrlen,
+ init_ifstats_l2, print_ifstats_l2,
+ MPLS_STATS_LINK,
+ XLAT_KNOWN(0x1, "MPLS_STATS_LINK"),
+ pattern, mls, print_quoted_hex, 2,
+ printf("{rx_packets=%llu, tx_packets=%llu"
+ ", rx_bytes=%llu, tx_bytes=%llu"
+ ", rx_errors=%llu, tx_errors=%llu"
+ ", rx_dropped=%llu"
+ ", tx_dropped=%llu"
+ ", rx_noroute=%llu}",
+ (ullong) mls.rx_packets,
+ (ullong) mls.tx_packets,
+ (ullong) mls.rx_bytes,
+ (ullong) mls.tx_bytes,
+ (ullong) mls.rx_errors,
+ (ullong) mls.tx_errors,
+ (ullong) mls.rx_dropped,
+ (ullong) mls.tx_dropped,
+ (ullong) mls.rx_noroute));
+
+ char mls_buf[sizeof(mls) + 32];
+ fill_memory(mls_buf, sizeof(mls_buf));
+ memcpy(mls_buf, &mls, sizeof(mls));
+ TEST_NLATTR_(fd, nlh0, hdrlen + 2 * NLA_HDRLEN,
+ init_ifstats_l2, print_ifstats_l2,
+ MPLS_STATS_LINK, XLAT_KNOWN(0x1, "MPLS_STATS_LINK"),
+ sizeof(mls_buf), mls_buf, sizeof(mls_buf),
+ printf("{rx_packets=9264081114510713072"
+ ", tx_packets=9264081114510713073"
+ ", rx_bytes=9264081114510713074"
+ ", tx_bytes=9264081114510713075"
+ ", rx_errors=9264081114510713076"
+ ", tx_errors=9264081114510713077"
+ ", rx_dropped=9264081114510713078"
+ ", tx_dropped=9264081114510713079"
+ ", rx_noroute=9264081114510713080}, ");
+ print_quoted_hex(mls_buf + sizeof(mls), 32);
+ printf("]]"));
+}
+
+int
+main(void)
+{
+ skip_if_unavailable("/proc/self/fd/");
+
+ const int fd = create_nl_socket(NETLINK_ROUTE);
+ void *nlh0 = midtail_alloc(NLMSG_SPACE(hdrlen),
+ NLA_HDRLEN + 256);
+
+ fill_memory_ex(pattern, sizeof(pattern), 'a', 'z' - 'a' + 1);
+
+ /* Unknown attrs. */
+ static const uint16_t unk_types[] = { 6, 0xffff & NLA_TYPE_MASK };
+ for (size_t i = 0; i < ARRAY_SIZE(unk_types); i++) {
+ sprintf(nla_type_str, "%#x" NRAW(" /* IFLA_STATS_??? */"),
+ unk_types[i]);
+ TEST_NLATTR_(fd, nlh0, hdrlen,
+ init_ifstats, print_ifstats,
+ unk_types[i], nla_type_str,
+ 4, pattern, 4,
+ print_quoted_hex(pattern, 4));
+ }
+
+
+ /* IFLA_STATS_UNSPEC: unimplemented, no semantics. */
+ static const struct strval32 unimp_types[] = {
+ { ARG_XLAT_KNOWN(0, "IFLA_STATS_UNSPEC") },
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(unimp_types); i++) {
+ TEST_NLATTR_(fd, nlh0, hdrlen,
+ init_ifstats, print_ifstats,
+ unimp_types[i].val, unimp_types[i].str,
+ 42, pattern, 42,
+ print_quoted_hex(pattern, 32);
+ printf("..."));
+ }
+
+
+ /* IFLA_STATS_LINK_64 */
+ check_stats_64(fd, ARG_STR(IFLA_STATS_LINK_64), false);
+
+
+ /* IFLA_STATS_LINK_XSTATS, IFLA_STATS_LINK_XSTATS_SLAVE */
+ check_xstats(fd, ARG_STR(IFLA_STATS_LINK_XSTATS));
+ check_xstats(fd, ARG_STR(IFLA_STATS_LINK_XSTATS_SLAVE));
+
+
+ /* IFLA_STATS_LINK_OFFLOAD_STATS */
+ check_stats_offload(fd);
+
+
+ /* IFLA_STATS_AF_SPEC */
+ static const uint8_t af_spec_fams[] = { AF_MPLS };
+ check_stats_af_generic(fd, ARG_STR(IFLA_STATS_AF_SPEC),
+ ARRSZ_PAIR(af_spec_fams));
+
+ /* IFLA_STATS_AF_SPEC: AF_MPLS */
+ check_stats_af_mpls(fd);
+
+
+ puts("+++ exited with 0 +++");
+ return 0;
+}
diff --git a/tests/pure_executables.list b/tests/pure_executables.list
index 73d23a336..284209d6b 100755
--- a/tests/pure_executables.list
+++ b/tests/pure_executables.list
@@ -394,6 +394,10 @@ nlattr_ifla_xdp
nlattr_inet_diag_msg
nlattr_inet_diag_req_compat
nlattr_inet_diag_req_v2
+nlattr_ifstats
+nlattr_ifstats-Xabbrev
+nlattr_ifstats-Xraw
+nlattr_ifstats-Xverbose
nlattr_mdba_mdb_entry
nlattr_mdba_router_port
nlattr_ndmsg