lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Thu, 21 Mar 2019 14:40:57 +0100 (CET)
From:   Michal Kubecek <mkubecek@...e.cz>
To:     David Miller <davem@...emloft.net>, netdev@...r.kernel.org
Cc:     Jakub Kicinski <jakub.kicinski@...ronome.com>,
        Jiri Pirko <jiri@...nulli.us>, Andrew Lunn <andrew@...n.ch>,
        Florian Fainelli <f.fainelli@...il.com>,
        John Linville <linville@...driver.com>,
        linux-kernel@...r.kernel.org
Subject: [PATCH net-next v4 13/22] ethtool: provide driver/device information
 in GET_INFO request

Implement GET_INFO request to get basic driver and device information as
provided by ETHTOOL_GDRVINFO ioct command. The information is read only so
that the corresponding SET_INFO message is only used in kernel replies.

Move most of ethtool_get_drvinfo() int common.c so that the code can be
shared by both ioctl and netlink interface.

Signed-off-by: Michal Kubecek <mkubecek@...e.cz>
---
 Documentation/networking/ethtool-netlink.txt |  43 ++++-
 include/uapi/linux/ethtool_netlink.h         |  30 ++++
 net/ethtool/Makefile                         |   2 +-
 net/ethtool/common.c                         |  52 ++++++
 net/ethtool/common.h                         |   2 +
 net/ethtool/info.c                           | 158 +++++++++++++++++++
 net/ethtool/ioctl.c                          |  50 +-----
 net/ethtool/netlink.c                        |   8 +
 net/ethtool/netlink.h                        |   1 +
 9 files changed, 299 insertions(+), 47 deletions(-)
 create mode 100644 net/ethtool/info.c

diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 1508c16a236e..cffa508ca6c6 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -129,6 +129,8 @@ List of message types
     ETHNL_CMD_EVENT			notification only
     ETHNL_CMD_GET_STRSET
     ETHNL_CMD_SET_STRSET		response only
+    ETHNL_CMD_GET_INFO
+    ETHNL_CMD_SET_INFO			response only
 
 All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
 to indicate the type.
@@ -209,6 +211,45 @@ ETHA_STRSET_COUNTS tells kernel to only return string counts of the sets, not
 the actual strings.
 
 
+GET_INFO
+--------
+
+GET_INFO requests information provided by ioctl commands ETHTOOL_GDRVINFO,
+ETHTOOL_GPERMADDR and ETHTOOL_GET_TS_INFO to provide basic device information.
+Common pattern is that all information is read only so that SET_INFO message
+exists but is only used by kernel for replies to GET_INFO requests. There is
+also no corresponding notification.
+
+Request contents:
+
+    ETHA_INFO_DEV		(nested)	device identification
+    ETHA_INFO_INFOMASK		(u32)		info mask
+    ETHA_INFO_COMPACT		(flag)		request compact bitsets
+
+Info mask bits meaning:
+
+    ETH_INFO_IM_DRVINFO			driver info (GDRVINFO)
+    ETH_INFO_IM_PERMADDR		permanent HW address (GPERMADDR)
+    ETH_INFO_IM_TSINFO			timestamping info (GET_TS_INFO)
+
+Kernel response contents:
+
+    ETHA_INFO_DEV		(nested)	device identification
+    ETHA_INFO_DRVINFO		(nested)	driver information
+        ETHA_DRVINFO_DRIVER		(string)	driver name
+        ETHA_DRVINFO_FWVERSION		(string)	firmware version
+        ETHA_DRVINFO_BUSINFO		(string)	device bus address
+        ETHA_DRVINFO_EROM_VER		(string)	expansion ROM version
+
+The meaning of DRVINFO attributes follows the corresponding fields of
+ETHTOOL_GDRVINFO response. Second part with various counts and sizes is
+omitted as these are not really needed (and if they are, they can be easily
+found by different means). Driver version is also omitted as it is rather
+misleading in most cases.
+
+GET_INFO requests allow dumps.
+
+
 Request translation
 -------------------
 
@@ -220,7 +261,7 @@ ioctl command			netlink command
 ---------------------------------------------------------------------
 ETHTOOL_GSET			n/a
 ETHTOOL_SSET			n/a
-ETHTOOL_GDRVINFO		n/a
+ETHTOOL_GDRVINFO		ETHNL_CMD_GET_INFO
 ETHTOOL_GREGS			n/a
 ETHTOOL_GWOL			n/a
 ETHTOOL_SWOL			n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 66aeb436b822..386d4273ac44 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -16,6 +16,8 @@ enum {
 	ETHNL_CMD_EVENT,		/* only for notifications */
 	ETHNL_CMD_GET_STRSET,
 	ETHNL_CMD_SET_STRSET,		/* only for reply */
+	ETHNL_CMD_GET_INFO,
+	ETHNL_CMD_SET_INFO,		/* only for reply */
 
 	__ETHNL_CMD_CNT,
 	ETHNL_CMD_MAX = (__ETHNL_CMD_CNT - 1)
@@ -141,6 +143,34 @@ enum {
 	ETHA_STRSET_MAX = (__ETHA_STRSET_CNT - 1)
 };
 
+/* GET_INFO / SET_INFO */
+
+enum {
+	ETHA_INFO_UNSPEC,
+	ETHA_INFO_DEV,				/* nest - ETHA_DEV_* */
+	ETHA_INFO_INFOMASK,			/* u32 */
+	ETHA_INFO_COMPACT,			/* flag */
+	ETHA_INFO_DRVINFO,			/* nest - ETHA_DRVINFO_* */
+
+	__ETHA_INFO_CNT,
+	ETHA_INFO_MAX = (__ETHA_INFO_CNT - 1)
+};
+
+#define ETH_INFO_IM_DRVINFO			(1U << 0)
+
+#define ETH_INFO_IM_ALL (ETH_INFO_IM_DRVINFO)
+
+enum {
+	ETHA_DRVINFO_UNSPEC,
+	ETHA_DRVINFO_DRIVER,			/* string */
+	ETHA_DRVINFO_FWVERSION,			/* string */
+	ETHA_DRVINFO_BUSINFO,			/* string */
+	ETHA_DRVINFO_EROM_VER,			/* string */
+
+	__ETHA_DRVINFO_CNT,
+	ETHA_DRVINFO_MAX = (__ETHA_DRVINFO_CNT - 1)
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 11ceb00821b3..96d41dc45d4f 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,4 @@ obj-y				+= ioctl.o common.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
 
-ethtool_nl-y	:= netlink.o bitset.o strset.o
+ethtool_nl-y	:= netlink.o bitset.o strset.o info.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 73f721a1c557..8da992129321 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
 
+#include <linux/rtnetlink.h>
+#include <net/devlink.h>
 #include "common.h"
 
 const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = {
@@ -81,3 +83,53 @@ phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
 	[ETHTOOL_ID_UNSPEC]     = "Unspec",
 	[ETHTOOL_PHY_DOWNSHIFT]	= "phy-downshift",
 };
+
+int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
+{
+	const struct ethtool_ops *ops = dev->ethtool_ops;
+
+	memset(info, 0, sizeof(*info));
+	info->cmd = ETHTOOL_GDRVINFO;
+	if (ops->get_drvinfo) {
+		ops->get_drvinfo(dev, info);
+	} else if (dev->dev.parent && dev->dev.parent->driver) {
+		strlcpy(info->bus_info, dev_name(dev->dev.parent),
+			sizeof(info->bus_info));
+		strlcpy(info->driver, dev->dev.parent->driver->name,
+			sizeof(info->driver));
+	} else {
+		return -EOPNOTSUPP;
+	}
+
+	/* this method of obtaining string set info is deprecated;
+	 * Use ETHTOOL_GSSET_INFO instead.
+	 */
+	if (ops->get_sset_count) {
+		int rc;
+
+		rc = ops->get_sset_count(dev, ETH_SS_TEST);
+		if (rc >= 0)
+			info->testinfo_len = rc;
+		rc = ops->get_sset_count(dev, ETH_SS_STATS);
+		if (rc >= 0)
+			info->n_stats = rc;
+		rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
+		if (rc >= 0)
+			info->n_priv_flags = rc;
+	}
+	if (ops->get_regs_len) {
+		int ret = ops->get_regs_len(dev);
+
+		if (ret > 0)
+			info->regdump_len = ret;
+	}
+
+	if (ops->get_eeprom_len)
+		info->eedump_len = ops->get_eeprom_len(dev);
+
+	if (!info->fw_version[0])
+		devlink_compat_running_version(dev, info->fw_version,
+					       sizeof(info->fw_version));
+
+	return 0;
+}
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 41b2efc1e4e1..e87e58b3a274 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -3,6 +3,7 @@
 #ifndef _ETHTOOL_COMMON_H
 #define _ETHTOOL_COMMON_H
 
+#include <linux/netdevice.h>
 #include <linux/ethtool.h>
 
 extern const char
@@ -14,4 +15,5 @@ tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN];
 extern const char
 phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
 
+int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info);
 #endif /* _ETHTOOL_COMMON_H */
diff --git a/net/ethtool/info.c b/net/ethtool/info.c
new file mode 100644
index 000000000000..cc42993bb05b
--- /dev/null
+++ b/net/ethtool/info.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include "netlink.h"
+#include "common.h"
+#include "bitset.h"
+
+struct info_data {
+	struct common_req_info		reqinfo_base;
+
+	/* everything below here will be reset for each device in dumps */
+	struct common_reply_data	repdata_base;
+	struct ethtool_drvinfo		drvinfo;
+};
+
+static const struct nla_policy get_info_policy[ETHA_INFO_MAX + 1] = {
+	[ETHA_INFO_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHA_INFO_DEV]			= { .type = NLA_NESTED },
+	[ETHA_INFO_INFOMASK]		= { .type = NLA_U32 },
+	[ETHA_INFO_COMPACT]		= { .type = NLA_FLAG },
+	[ETHA_INFO_DRVINFO]		= { .type = NLA_REJECT },
+};
+
+/* parse_request() handler */
+static int parse_info(struct common_req_info *req_info, struct sk_buff *skb,
+		      struct genl_info *info, const struct nlmsghdr *nlhdr)
+{
+	struct nlattr *tb[ETHA_INFO_MAX + 1];
+	int ret;
+
+	ret = ethnlmsg_parse(nlhdr, tb, ETHA_INFO_MAX, get_info_policy, info);
+	if (ret < 0)
+		return ret;
+
+	if (tb[ETHA_INFO_DEV]) {
+		req_info->dev = ethnl_dev_get(info, tb[ETHA_INFO_DEV]);
+		if (IS_ERR(req_info->dev)) {
+			ret = PTR_ERR(req_info->dev);
+			req_info->dev = NULL;
+			return ret;
+		}
+	}
+	if (tb[ETHA_INFO_INFOMASK])
+		req_info->req_mask = nla_get_u32(tb[ETHA_INFO_INFOMASK]);
+	if (tb[ETHA_INFO_COMPACT])
+		req_info->compact = true;
+	if (req_info->req_mask == 0)
+		req_info->req_mask = ETH_INFO_IM_ALL;
+
+	return 0;
+}
+
+/* prepare_data() handler */
+static int prepare_info(struct common_req_info *req_info,
+			struct genl_info *info)
+{
+	struct info_data *data =
+		container_of(req_info, struct info_data, reqinfo_base);
+	struct net_device *dev = data->repdata_base.dev;
+	u32 req_mask = req_info->req_mask & ETH_INFO_IM_ALL;
+	int ret;
+
+	ret = ethnl_before_ops(dev);
+	if (ret < 0)
+		return ret;
+	if (req_mask & ETH_INFO_IM_DRVINFO) {
+		ret = __ethtool_get_drvinfo(dev, &data->drvinfo);
+		if (ret < 0)
+			req_mask &= ~ETH_INFO_IM_DRVINFO;
+	}
+	ethnl_after_ops(dev);
+
+	data->repdata_base.info_mask = req_mask;
+	if (req_info->req_mask & ~req_mask)
+		warn_partial_info(info);
+	return 0;
+}
+
+static int drvinfo_size(const struct ethtool_drvinfo *drvinfo)
+{
+	int len = 0;
+
+	len += ethnl_str_ifne_size(drvinfo->driver);
+	len += ethnl_str_ifne_size(drvinfo->fw_version);
+	len += ethnl_str_ifne_size(drvinfo->bus_info);
+	len += ethnl_str_ifne_size(drvinfo->erom_version);
+
+	return nla_total_size(len);
+}
+
+/* reply_size() handler */
+static int info_size(const struct common_req_info *req_info)
+{
+	const struct info_data *data =
+		container_of(req_info, struct info_data, reqinfo_base);
+	u32 info_mask = data->repdata_base.info_mask;
+	int len = 0;
+
+	len += dev_ident_size();
+	if (info_mask & ETH_INFO_IM_DRVINFO)
+		len += drvinfo_size(&data->drvinfo);
+
+	return len;
+}
+
+static int fill_drvinfo(struct sk_buff *skb,
+			const struct ethtool_drvinfo *drvinfo)
+{
+	struct nlattr *nest = ethnl_nest_start(skb, ETHA_INFO_DRVINFO);
+	int ret;
+
+	if (!nest)
+		return -EMSGSIZE;
+	ret = -EMSGSIZE;
+	if (ethnl_put_str_ifne(skb, ETHA_DRVINFO_DRIVER, drvinfo->driver) ||
+	    ethnl_put_str_ifne(skb, ETHA_DRVINFO_FWVERSION,
+			       drvinfo->fw_version) ||
+	    ethnl_put_str_ifne(skb, ETHA_DRVINFO_BUSINFO, drvinfo->bus_info) ||
+	    ethnl_put_str_ifne(skb, ETHA_DRVINFO_EROM_VER,
+			       drvinfo->erom_version))
+		goto err;
+
+	nla_nest_end(skb, nest);
+	return 0;
+err:
+	nla_nest_cancel(skb, nest);
+	return ret;
+}
+
+/* fill_reply() handler */
+static int fill_info(struct sk_buff *skb,
+		     const struct common_req_info *req_info)
+{
+	const struct info_data *data =
+		container_of(req_info, struct info_data, reqinfo_base);
+	u32 info_mask = data->repdata_base.info_mask;
+	int ret;
+
+	if (info_mask & ETH_INFO_IM_DRVINFO) {
+		ret = fill_drvinfo(skb, &data->drvinfo);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+const struct get_request_ops info_request_ops = {
+	.request_cmd		= ETHNL_CMD_GET_INFO,
+	.reply_cmd		= ETHNL_CMD_SET_INFO,
+	.dev_attrtype		= ETHA_INFO_DEV,
+	.data_size		= sizeof(struct info_data),
+	.repdata_offset		= offsetof(struct info_data, repdata_base),
+
+	.parse_request		= parse_info,
+	.prepare_data		= prepare_info,
+	.reply_size		= info_size,
+	.fill_reply		= fill_info,
+};
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 04d747056070..844a4f4b1552 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -685,54 +685,14 @@ static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev,
 						  void __user *useraddr)
 {
 	struct ethtool_drvinfo info;
-	const struct ethtool_ops *ops = dev->ethtool_ops;
-
-	memset(&info, 0, sizeof(info));
-	info.cmd = ETHTOOL_GDRVINFO;
-	if (ops->get_drvinfo) {
-		ops->get_drvinfo(dev, &info);
-	} else if (dev->dev.parent && dev->dev.parent->driver) {
-		strlcpy(info.bus_info, dev_name(dev->dev.parent),
-			sizeof(info.bus_info));
-		strlcpy(info.driver, dev->dev.parent->driver->name,
-			sizeof(info.driver));
-	} else {
-		return -EOPNOTSUPP;
-	}
-
-	/*
-	 * this method of obtaining string set info is deprecated;
-	 * Use ETHTOOL_GSSET_INFO instead.
-	 */
-	if (ops->get_sset_count) {
-		int rc;
-
-		rc = ops->get_sset_count(dev, ETH_SS_TEST);
-		if (rc >= 0)
-			info.testinfo_len = rc;
-		rc = ops->get_sset_count(dev, ETH_SS_STATS);
-		if (rc >= 0)
-			info.n_stats = rc;
-		rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
-		if (rc >= 0)
-			info.n_priv_flags = rc;
-	}
-	if (ops->get_regs_len) {
-		int ret = ops->get_regs_len(dev);
-
-		if (ret > 0)
-			info.regdump_len = ret;
-	}
-
-	if (ops->get_eeprom_len)
-		info.eedump_len = ops->get_eeprom_len(dev);
-
-	if (!info.fw_version[0])
-		devlink_compat_running_version(dev, info.fw_version,
-					       sizeof(info.fw_version));
+	int rc;
 
+	rc = __ethtool_get_drvinfo(dev, &info);
+	if (rc < 0)
+		return rc;
 	if (copy_to_user(useraddr, &info, sizeof(info)))
 		return -EFAULT;
+
 	return 0;
 }
 
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 3559f5c7040f..ab30f77eda25 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -153,6 +153,7 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
 
 const struct get_request_ops *get_requests[__ETHNL_CMD_CNT] = {
 	[ETHNL_CMD_GET_STRSET]		= &strset_request_ops,
+	[ETHNL_CMD_GET_INFO]		= &info_request_ops,
 };
 
 /**
@@ -564,6 +565,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
 		.dumpit	= ethnl_get_dumpit,
 		.done	= ethnl_get_done,
 	},
+	{
+		.cmd	= ETHNL_CMD_GET_INFO,
+		.doit	= ethnl_get_doit,
+		.start	= ethnl_get_start,
+		.dumpit	= ethnl_get_dumpit,
+		.done	= ethnl_get_done,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 32d85bb5c49a..48980ae9e963 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -278,5 +278,6 @@ struct get_request_ops {
 /* request handlers */
 
 extern const struct get_request_ops strset_request_ops;
+extern const struct get_request_ops info_request_ops;
 
 #endif /* _NET_ETHTOOL_NETLINK_H */
-- 
2.21.0

Powered by blists - more mailing lists