[RFC PATCH net-next v3 20/21] ethtool: provide private flags in GET_SETTINGS request

From: Michal Kubecek
Date: Mon Feb 18 2019 - 13:23:14 EST


Add information about device private flags (as provided by
ETHTOOL_GPFLAGS ioctl command) in GET_SETTINGS reply when
ETH_SETTINGS_IM_PRIVFLAGS flag is set in the request.

Signed-off-by: Michal Kubecek <mkubecek@xxxxxxx>
---
Documentation/networking/ethtool-netlink.txt | 9 +-
include/uapi/linux/ethtool_netlink.h | 4 +-
net/ethtool/settings.c | 98 ++++++++++++++++++++
3 files changed, 109 insertions(+), 2 deletions(-)

diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 664c922a05eb..290008aaed0a 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -287,6 +287,7 @@ Info mask bits meaning:
ETH_SETTINGS_IM_MSGLEVEL msglevel
ETH_SETTINGS_IM_LINK link state
ETH_SETTINGS_IM_FEATURES features
+ ETH_SETTINGS_IM_PRIVFLAGS device private flags

Response contents:

@@ -312,6 +313,7 @@ Response contents:
ETHA_FEATURES_WANTED (bitset) dev->wanted_features
ETHA_FEATURES_ACTIVE (bitset) dev->features
ETHA_FEATURES_NOCHANGE (bitset) NETIF_F_NEVER_CHANGE
+ ETHA_SETTINGS_PRIV_FLAGS (bitset) device private flags

Most of the attributes and their values have the same meaning as matching
members of the corresponding ioctl structures. For ETHA_SETTINGS_LINK_MODES,
@@ -333,6 +335,11 @@ itself. ETHA_FEATURES_HW uses mask consisting of all features recognized by
kernel (to provide all names when using verbose bitmap format), remaining
three use mask equal to value (to save space).

+ETHA_SETTINGS_PRIV_FLAGS is a bitset with values of device private flags.
+These flags are defined by driver, their number and names (as well as meaning)
+are device dependent. For compact bitset format, names can be retrieved as
+ETH_SS_PRIV_FLAGS string set.
+
GET_SETTINGS request is allowed for unprivileged user but ETHA_SETTINGS_SOPASS
is only provided by kernel in response to privileged (netns CAP_NET_ADMIN)
requests.
@@ -389,7 +396,7 @@ ETHTOOL_GGSO ETHNL_CMD_GET_SETTINGS
ETHTOOL_SGSO n/a
ETHTOOL_GFLAGS ETHNL_CMD_GET_SETTINGS
ETHTOOL_SFLAGS n/a
-ETHTOOL_GPFLAGS n/a
+ETHTOOL_GPFLAGS ETHNL_CMD_GET_SETTINGS
ETHTOOL_SPFLAGS n/a
ETHTOOL_GRXFH n/a
ETHTOOL_SRXFH n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 154d7e6a59dd..afd61d36bcf8 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -205,6 +205,7 @@ enum {
ETHA_SETTINGS_MSGLEVEL, /* bitfield32 */
ETHA_SETTINGS_LINK, /* u8 */
ETHA_SETTINGS_FEATURES, /* nest - ETHA_FEATURES_* */
+ ETHA_SETTINGS_PRIV_FLAGS, /* nest - ETHA_BITSET_* */

__ETHA_SETTINGS_CNT,
ETHA_SETTINGS_MAX = (__ETHA_SETTINGS_CNT - 1)
@@ -216,8 +217,9 @@ enum {
#define ETH_SETTINGS_IM_MSGLEVEL 0x08
#define ETH_SETTINGS_IM_LINK 0x10
#define ETH_SETTINGS_IM_FEATURES 0x20
+#define ETH_SETTINGS_IM_PRIVFLAGS 0x40

-#define ETH_SETTINGS_IM_ALL 0x3f
+#define ETH_SETTINGS_IM_ALL 0x7f

enum {
ETHA_FEATURES_UNSPEC,
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
index 32ee273de879..7f72f4250306 100644
--- a/net/ethtool/settings.c
+++ b/net/ethtool/settings.c
@@ -22,6 +22,9 @@ struct settings_data {
u32 active[ETHTOOL_DEV_FEATURE_WORDS];
u32 nochange[ETHTOOL_DEV_FEATURE_WORDS];
} features;
+ char (*priv_flag_names)[ETH_GSTRING_LEN];
+ u32 priv_flags;
+ unsigned int n_priv_flags;
};

static const struct nla_policy get_settings_policy[ETHA_SETTINGS_MAX + 1] = {
@@ -36,6 +39,7 @@ static const struct nla_policy get_settings_policy[ETHA_SETTINGS_MAX + 1] = {
[ETHA_SETTINGS_MSGLEVEL] = { .type = NLA_REJECT },
[ETHA_SETTINGS_LINK] = { .type = NLA_REJECT },
[ETHA_SETTINGS_FEATURES] = { .type = NLA_REJECT },
+ [ETHA_SETTINGS_PRIV_FLAGS] = { .type = NLA_REJECT },
};

static int parse_settings(struct common_req_info *req_info,
@@ -114,6 +118,58 @@ static int ethnl_get_features(struct net_device *dev,
return 0;
}

+static int get_priv_flags_info(struct net_device *dev, unsigned int *count,
+ void **names)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ int nflags;
+
+ if (!ops->get_priv_flags || !ops->get_sset_count || !ops->get_strings)
+ return -EOPNOTSUPP;
+ nflags = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
+ if (nflags < 0)
+ return nflags;
+
+ if (names) {
+ *names = kcalloc(nflags, ETH_GSTRING_LEN, GFP_KERNEL);
+ if (!*names)
+ return -ENOMEM;
+ ops->get_strings(dev, ETH_SS_PRIV_FLAGS, *names);
+ }
+
+ /* We can easily pass more than 32 private flags to userspace via
+ * netlink but we cannot get more with ethtool_ops::get_priv_flags().
+ * Note that we must not adjust nflags before allocating the space
+ * for flag names as the buffer must be large enough for all flags.
+ */
+ if (WARN_ONCE(nflags > 32,
+ "device %s reports more than 32 private flags (%d)\n",
+ netdev_name(dev), nflags))
+ nflags = 32;
+
+ *count = nflags;
+ return 0;
+}
+
+static int ethnl_get_priv_flags(struct genl_info *info,
+ struct settings_data *data)
+{
+ struct net_device *dev = data->repdata_base.dev;
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ unsigned int nflags;
+ void *names;
+ int ret;
+
+ ret = get_priv_flags_info(dev, &nflags, &names);
+ if (ret < 0)
+ return ret;
+
+ data->priv_flags = ops->get_priv_flags(dev);
+ data->priv_flag_names = names;
+ data->n_priv_flags = nflags;
+ return 0;
+}
+
static int prepare_settings(struct common_req_info *req_info,
struct genl_info *info)
{
@@ -163,6 +219,11 @@ static int prepare_settings(struct common_req_info *req_info,
data->link = __ethtool_get_link(dev);
if (req_mask & ETH_SETTINGS_IM_FEATURES)
ethnl_get_features(dev, data);
+ if (req_mask & ETH_SETTINGS_IM_PRIVFLAGS) {
+ ret = ethnl_get_priv_flags(info, data);
+ if (ret < 0)
+ req_mask &= ~ETH_SETTINGS_IM_PRIVFLAGS;
+ }
ethnl_after_ops(dev);

data->repdata_base.info_mask = req_mask;
@@ -281,6 +342,17 @@ static int settings_size(const struct common_req_info *req_info)
return ret;
len += ret;
}
+ if (info_mask & ETH_SETTINGS_IM_PRIVFLAGS) {
+ const unsigned int flags =
+ (compact ? ETHNL_BITSET_COMPACT : 0) |
+ ETHNL_BITSET_LEGACY_NAMES;
+
+ ret = ethnl_bitset32_size(data->n_priv_flags, &data->priv_flags,
+ NULL, data->priv_flag_names, flags);
+ if (ret < 0)
+ return ret;
+ len += ret;
+ }

return len;
}
@@ -404,6 +476,18 @@ static int fill_features(struct sk_buff *skb, const struct settings_data *data)
return 0;
}

+static int fill_priv_flags(struct sk_buff *skb,
+ const struct settings_data *data)
+{
+ const unsigned int bitset_flags =
+ (data->reqinfo_base.compact ? ETHNL_BITSET_COMPACT : 0) |
+ ETHNL_BITSET_LEGACY_NAMES;
+
+ return ethnl_put_bitset32(skb, ETHA_SETTINGS_PRIV_FLAGS,
+ data->n_priv_flags, &data->priv_flags, NULL,
+ data->priv_flag_names, bitset_flags);
+}
+
static int fill_settings(struct sk_buff *skb,
const struct common_req_info *req_info)
{
@@ -443,10 +527,23 @@ static int fill_settings(struct sk_buff *skb,
if (ret < 0)
return ret;
}
+ if (info_mask & ETH_SETTINGS_IM_PRIVFLAGS) {
+ ret = fill_priv_flags(skb, data);
+ if (ret < 0)
+ return ret;
+ }

return 0;
}

+static void settings_cleanup(struct common_req_info *req_info)
+{
+ const struct settings_data *data =
+ container_of(req_info, struct settings_data, reqinfo_base);
+
+ kfree(data->priv_flag_names);
+}
+
const struct get_request_ops settings_request_ops = {
.request_cmd = ETHNL_CMD_GET_SETTINGS,
.reply_cmd = ETHNL_CMD_SET_SETTINGS,
@@ -458,4 +555,5 @@ const struct get_request_ops settings_request_ops = {
.prepare_data = prepare_settings,
.reply_size = settings_size,
.fill_reply = fill_settings,
+ .cleanup = settings_cleanup,
};
--
2.20.1