Older kernels don't support statistics, to avoid retries
make use of netlink policy dumps to figure out which
flags kernel actually supports.

Signed-off-by: Jakub Kicinski <k...@kernel.org>
---
 netlink/msgbuff.h |   6 ++
 netlink/netlink.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++
 netlink/netlink.h |   2 +
 3 files changed, 159 insertions(+)

diff --git a/netlink/msgbuff.h b/netlink/msgbuff.h
index 24b99c5a28d7..7d6731fc24a3 100644
--- a/netlink/msgbuff.h
+++ b/netlink/msgbuff.h
@@ -81,6 +81,12 @@ static inline bool ethnla_put_u32(struct nl_msg_buff 
*msgbuff, uint16_t type,
        return ethnla_put(msgbuff, type, sizeof(uint32_t), &data);
 }
 
+static inline bool ethnla_put_u16(struct nl_msg_buff *msgbuff, uint16_t type,
+                                 uint16_t data)
+{
+       return ethnla_put(msgbuff, type, sizeof(uint16_t), &data);
+}
+
 static inline bool ethnla_put_u8(struct nl_msg_buff *msgbuff, uint16_t type,
                                 uint8_t data)
 {
diff --git a/netlink/netlink.c b/netlink/netlink.c
index e42d57076a4b..f79da12d3eee 100644
--- a/netlink/netlink.c
+++ b/netlink/netlink.c
@@ -135,6 +135,157 @@ bool netlink_cmd_check(struct cmd_context *ctx, unsigned 
int cmd,
        return !(nlctx->ops_flags[cmd] & cap);
 }
 
+struct ethtool_op_policy_query_ctx {
+       struct nl_context *nlctx;
+       unsigned int op;
+       unsigned int op_hdr_attr;
+
+       bool op_policy_found;
+       bool hdr_policy_found;
+       unsigned int op_policy_idx;
+       unsigned int hdr_policy_idx;
+       uint64_t flag_mask;
+};
+
+static int family_policy_find_op(struct ethtool_op_policy_query_ctx *ctx,
+                                const struct nlattr *op_policy)
+{
+       const struct nlattr *attr;
+       unsigned int type;
+       int ret;
+
+       type = ctx->nlctx->is_dump ?
+               CTRL_ATTR_POLICY_DUMP : CTRL_ATTR_POLICY_DO;
+
+       mnl_attr_for_each_nested(attr, op_policy) {
+               const struct nlattr *tb[CTRL_ATTR_POLICY_DUMP_MAX + 1] = {};
+               DECLARE_ATTR_TB_INFO(tb);
+
+               if (mnl_attr_get_type(attr) != ctx->op)
+                       continue;
+
+               ret = mnl_attr_parse_nested(attr, attr_cb, &tb_info);
+               if (ret < 0)
+                       return ret;
+
+               if (!tb[type])
+                       continue;
+
+               ctx->op_policy_found = true;
+               ctx->op_policy_idx = mnl_attr_get_u32(tb[type]);
+               break;
+       }
+
+       return 0;
+}
+
+static int family_policy_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+       const struct nlattr *tba[NL_POLICY_TYPE_ATTR_MAX + 1] = {};
+       DECLARE_ATTR_TB_INFO(tba);
+       const struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
+       DECLARE_ATTR_TB_INFO(tb);
+       const struct nlattr *policy_attr, *attr_attr, *attr;
+       struct ethtool_op_policy_query_ctx *ctx = data;
+       unsigned int attr_idx, policy_idx;
+       int ret;
+
+       ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+       if (ret < 0)
+               return MNL_CB_ERROR;
+
+       if (!ctx->op_policy_found) {
+               if (!tb[CTRL_ATTR_OP_POLICY]) {
+                       fprintf(stderr, "Error: op policy map not present\n");
+                       return MNL_CB_ERROR;
+               }
+               ret = family_policy_find_op(ctx, tb[CTRL_ATTR_OP_POLICY]);
+               return ret < 0 ? MNL_CB_ERROR : MNL_CB_OK;
+       }
+
+       if (!tb[CTRL_ATTR_POLICY])
+               return MNL_CB_OK;
+
+       policy_attr = mnl_attr_get_payload(tb[CTRL_ATTR_POLICY]);
+       policy_idx = mnl_attr_get_type(policy_attr);
+       attr_attr = mnl_attr_get_payload(policy_attr);
+       attr_idx = mnl_attr_get_type(attr_attr);
+
+       ret = mnl_attr_parse_nested(attr_attr, attr_cb, &tba_info);
+       if (ret < 0)
+               return MNL_CB_ERROR;
+
+       if (policy_idx == ctx->op_policy_idx && attr_idx == ctx->op_hdr_attr) {
+               attr = tba[NL_POLICY_TYPE_ATTR_POLICY_IDX];
+               if (!attr) {
+                       fprintf(stderr, "Error: no policy index in what was 
expected to be ethtool header attribute\n");
+                       return MNL_CB_ERROR;
+               }
+               ctx->hdr_policy_found = true;
+               ctx->hdr_policy_idx = mnl_attr_get_u32(attr);
+       }
+
+       if (ctx->hdr_policy_found && ctx->hdr_policy_idx == policy_idx &&
+           attr_idx == ETHTOOL_A_HEADER_FLAGS) {
+               attr = tba[NL_POLICY_TYPE_ATTR_MASK];
+               if (!attr) {
+                       fprintf(stderr, "Error: validation mask not reported 
for ethtool header flags\n");
+                       return MNL_CB_ERROR;
+               }
+
+               ctx->flag_mask = mnl_attr_get_u64(attr);
+       }
+
+       return MNL_CB_OK;
+}
+
+static int get_flags_policy(struct nl_context *nlctx, struct nl_socket *nlsk,
+                           unsigned int nlcmd, unsigned int hdrattr)
+{
+       struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+       struct ethtool_op_policy_query_ctx ctx;
+       int ret;
+
+       memset(&ctx, 0, sizeof(ctx));
+       ctx.nlctx = nlctx;
+       ctx.op = nlcmd;
+       ctx.op_hdr_attr = hdrattr;
+
+       ret = __msg_init(msgbuff, GENL_ID_CTRL, CTRL_CMD_GETPOLICY,
+                        NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, 1);
+       if (ret < 0)
+               return ret;
+       ret = -EMSGSIZE;
+       if (ethnla_put_u16(msgbuff, CTRL_ATTR_FAMILY_ID, nlctx->ethnl_fam))
+               return ret;
+       if (ethnla_put_u32(msgbuff, CTRL_ATTR_OP, nlcmd))
+               return ret;
+
+       nlsock_sendmsg(nlsk, NULL);
+       nlsock_process_reply(nlsk, family_policy_cb, &ctx);
+
+       ret = ctx.flag_mask;
+       return ret;
+}
+
+u32 get_stats_flag(struct nl_context *nlctx, unsigned int nlcmd,
+                  unsigned int hdrattr)
+{
+       int ret;
+
+       if (!nlctx->ctx->show_stats)
+               return 0;
+       if (nlcmd > ETHTOOL_MSG_USER_MAX ||
+           !(nlctx->ops_flags[nlcmd] & GENL_CMD_CAP_HASPOL))
+               return 0;
+
+       ret = get_flags_policy(nlctx, nlctx->ethnl_socket, nlcmd, hdrattr);
+       if (ret < 0)
+               return 0;
+
+       return ret & ETHTOOL_FLAG_STATS;
+}
+
 /* initialization */
 
 static int genl_read_ops(struct nl_context *nlctx,
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 1012e8e32cd8..799f2556cb31 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -66,6 +66,8 @@ bool netlink_cmd_check(struct cmd_context *ctx, unsigned int 
cmd,
                       bool allow_wildcard);
 const char *get_dev_name(const struct nlattr *nest);
 int get_dev_info(const struct nlattr *nest, int *ifindex, char *ifname);
+u32 get_stats_flag(struct nl_context *nlctx, unsigned int nlcmd,
+                  unsigned int hdrattr);
 
 int linkmodes_reply_cb(const struct nlmsghdr *nlhdr, void *data);
 int linkinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data);
-- 
2.26.2

Reply via email to