[PATCH net-next v5 19/22] ethtool: provide WoL information in GET_SETTINGS request

From: Michal Kubecek
Date: Mon Mar 25 2019 - 13:09:06 EST


Add information about supported and enabled wake on LAN modes into the
GET_SETTINGS reply when ETH_SETTINGS_IM_WOL flag is set in the request.

The GET_SETTINGS request can be still sent by unprivileged users but in
such case the SecureOn password (if any) is not included in the reply.

Send notification in the same format as reply SET_SETTINGS message when
wake on LAN settings are modified using ioctl interface (ETHTOOL_SWOL
command).

Signed-off-by: Michal Kubecek <mkubecek@xxxxxxx>
---
Documentation/networking/ethtool-netlink.txt | 13 +++-
include/uapi/linux/ethtool_netlink.h | 14 ++++-
net/ethtool/common.c | 10 ++++
net/ethtool/common.h | 1 +
net/ethtool/ioctl.c | 10 ++--
net/ethtool/settings.c | 63 ++++++++++++++++++++
6 files changed, 105 insertions(+), 6 deletions(-)

diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index ebd1a0404828..bc7f28f0182f 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -278,6 +278,7 @@ Info mask bits meaning:
ETH_SETTINGS_IM_LINKINFO link settings
ETH_SETTINGS_IM_LINKMODES link modes and related
ETH_SETTINGS_IM_LINKSTATE link state
+ ETH_SETTINGS_IM_WOL struct ethtool_wolinfo

Response contents:

@@ -296,12 +297,22 @@ Response contents:
ETHA_LINKMODES_DUPLEX (u8) duplex mode
ETHA_SETTINGS_LINK_STATE (nested) link state
ETHA_LINKSTATE_LINK (u8) link on/off/unknown
+ ETHA_SETTINGS_WOL (nested) wake on LAN settings
+ ETHA_WOL_MODES (bitfield32) wake on LAN modes
+ ETHA_WOL_SOPASS (binary) SecureOn(tm) password

Most of the attributes and their values have the same meaning as matching
members of the corresponding ioctl structures. For ETHA_LINKMODES_OURS,
value represents advertised modes and mask represents supported modes.
ETHA_LINKMODES_PEER in the reply is a bit list.

+For ETHA_WOL_MODES, selector reports wake on LAN modes supported by the
+device and value enabled modes.
+
+GET_SETTINGS request is allowed for unprivileged user but ETHA_WOL_SOPASS
+is only provided by kernel in response to privileged (netns CAP_NET_ADMIN)
+requests.
+
GET_SETTINGS requests allow dumps and messages in the same format as response
to them are broadcasted as notifications on change of these settings using
netlink or ioctl ethtool interface.
@@ -348,7 +359,7 @@ ETHTOOL_GSET ETHNL_CMD_GET_SETTINGS
ETHTOOL_SSET ETHNL_CMD_SET_SETTINGS
ETHTOOL_GDRVINFO ETHNL_CMD_GET_INFO
ETHTOOL_GREGS n/a
-ETHTOOL_GWOL n/a
+ETHTOOL_GWOL ETHNL_CMD_GET_SETTINGS
ETHTOOL_SWOL n/a
ETHTOOL_GMSGLVL n/a
ETHTOOL_SMSGLVL n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 45c27c7291d0..532ad11ae87c 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -197,6 +197,7 @@ enum {
ETHA_SETTINGS_LINK_INFO, /* nest - ETHA_LINKINFO_* */
ETHA_SETTINGS_LINK_MODES, /* nest - ETHA_LINKMODES_* */
ETHA_SETTINGS_LINK_STATE, /* nest - ETHA_LINKSTATE_* */
+ ETHA_SETTINGS_WOL, /* nest - ETHA_WOL_* */

__ETHA_SETTINGS_CNT,
ETHA_SETTINGS_MAX = (__ETHA_SETTINGS_CNT - 1)
@@ -205,10 +206,12 @@ enum {
#define ETH_SETTINGS_IM_LINKINFO (1U << 0)
#define ETH_SETTINGS_IM_LINKMODES (1U << 1)
#define ETH_SETTINGS_IM_LINKSTATE (1U << 2)
+#define ETH_SETTINGS_IM_WOL (1U << 3)

#define ETH_SETTINGS_IM_ALL (ETH_SETTINGS_IM_LINKINFO | \
ETH_SETTINGS_IM_LINKMODES | \
- ETH_SETTINGS_IM_LINKSTATE)
+ ETH_SETTINGS_IM_LINKSTATE | \
+ ETH_SETTINGS_IM_WOL)

enum {
ETHA_LINKINFO_UNSPEC,
@@ -242,6 +245,15 @@ enum {
ETHA_LINKSTATE_MAX = (__ETHA_LINKSTATE_CNT - 1)
};

+enum {
+ ETHA_WOL_UNSPEC,
+ ETHA_WOL_MODES, /* bitfield32 */
+ ETHA_WOL_SOPASS, /* binary */
+
+ __ETHA_WOL_CNT,
+ ETHA_WOL_MAX = (__ETHA_WOL_CNT - 1)
+};
+
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index dc907d8b6e43..4b2f08da910a 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -213,3 +213,13 @@ int __ethtool_get_link(struct net_device *dev)

return netif_running(dev) && dev->ethtool_ops->get_link(dev);
}
+
+int __ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+ if (!dev->ethtool_ops->get_wol)
+ return -EOPNOTSUPP;
+
+ dev->ethtool_ops->get_wol(dev, wol);
+
+ return 0;
+}
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index a5ddd7f5cfce..bbe3e51f7308 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -18,6 +18,7 @@ phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info);
int __ethtool_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info);
int __ethtool_get_link(struct net_device *dev);
+int __ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol);

bool convert_legacy_settings_to_link_ksettings(
struct ethtool_link_ksettings *link_ksettings,
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 58669dafeaf9..bba02a218eea 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -1242,11 +1242,11 @@ static int ethtool_reset(struct net_device *dev, char __user *useraddr)
static int ethtool_get_wol(struct net_device *dev, char __user *useraddr)
{
struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL };
+ int rc;

- if (!dev->ethtool_ops->get_wol)
- return -EOPNOTSUPP;
-
- dev->ethtool_ops->get_wol(dev, &wol);
+ rc = __ethtool_get_wol(dev, &wol);
+ if (rc < 0)
+ return rc;

if (copy_to_user(useraddr, &wol, sizeof(wol)))
return -EFAULT;
@@ -1269,6 +1269,8 @@ static int ethtool_set_wol(struct net_device *dev, char __user *useraddr)
return ret;

dev->wol_enabled = !!wol.wolopts;
+ ethtool_notify(dev, NULL, ETHNL_CMD_SET_SETTINGS, ETH_SETTINGS_IM_WOL,
+ NULL);

return 0;
}
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
index 7a2729f01f79..56a045e5e916 100644
--- a/net/ethtool/settings.c
+++ b/net/ethtool/settings.c
@@ -6,10 +6,12 @@

struct settings_data {
struct common_req_info reqinfo_base;
+ bool privileged;

/* everything below here will be reset for each device in dumps */
struct common_reply_data repdata_base;
struct ethtool_link_ksettings ksettings;
+ struct ethtool_wolinfo wolinfo;
struct ethtool_link_settings *lsettings;
int link;
bool lpm_empty;
@@ -114,15 +116,20 @@ static const struct nla_policy get_settings_policy[ETHA_SETTINGS_MAX + 1] = {
[ETHA_SETTINGS_LINK_INFO] = { .type = NLA_REJECT },
[ETHA_SETTINGS_LINK_MODES] = { .type = NLA_REJECT },
[ETHA_SETTINGS_LINK_STATE] = { .type = NLA_REJECT },
+ [ETHA_SETTINGS_WOL] = { .type = NLA_REJECT },
};

static int parse_settings(struct common_req_info *req_info,
struct sk_buff *skb, struct genl_info *info,
const struct nlmsghdr *nlhdr)
{
+ struct settings_data *data =
+ container_of(req_info, struct settings_data, reqinfo_base);
struct nlattr *tb[ETHA_SETTINGS_MAX + 1];
int ret;

+ data->privileged = ethnl_is_privileged(skb);
+
ret = ethnlmsg_parse(nlhdr, tb, ETHA_SETTINGS_MAX, get_settings_policy,
info);
if (ret < 0)
@@ -159,6 +166,16 @@ static int ethnl_get_link_ksettings(struct genl_info *info,
return ret;
}

+static int ethnl_get_wol(struct genl_info *info, struct net_device *dev,
+ struct ethtool_wolinfo *wolinfo)
+{
+ int ret = __ethtool_get_wol(dev, wolinfo);
+
+ if (ret < 0)
+ ETHNL_SET_ERRMSG(info, "failed to retrieve wol info");
+ return ret;
+}
+
static int prepare_settings(struct common_req_info *req_info,
struct genl_info *info)
{
@@ -194,6 +211,11 @@ static int prepare_settings(struct common_req_info *req_info,
}
if (req_mask & ETH_SETTINGS_IM_LINKSTATE)
data->link = __ethtool_get_link(dev);
+ if (req_mask & ETH_SETTINGS_IM_WOL) {
+ ret = ethnl_get_wol(info, dev, &data->wolinfo);
+ if (ret < 0)
+ req_mask &= ~ETH_SETTINGS_IM_WOL;
+ }
ethnl_after_ops(dev);

data->repdata_base.info_mask = req_mask;
@@ -249,6 +271,12 @@ static int link_state_size(int link)
return nla_total_size(nla_total_size(sizeof(u8)));
}

+static int wol_size(void)
+{
+ return nla_total_size(nla_total_size(sizeof(struct nla_bitfield32)) +
+ nla_total_size(SOPASS_MAX));
+}
+
/* To keep things simple, reserve space for some attributes which may not
* be added to the message (e.g. ETHA_SETTINGS_SOPASS); therefore the length
* returned may be bigger than the actual length of the message sent
@@ -272,6 +300,8 @@ static int settings_size(const struct common_req_info *req_info)
}
if (info_mask & ETH_SETTINGS_IM_LINKSTATE)
len += link_state_size(data->link);
+ if (info_mask & ETH_SETTINGS_IM_WOL)
+ len += wol_size();

return len;
}
@@ -361,6 +391,33 @@ static int fill_link_state(struct sk_buff *skb, u8 link)
return -EMSGSIZE;
}

+static int fill_wolinfo(struct sk_buff *skb,
+ const struct ethtool_wolinfo *wolinfo, bool privileged)
+{
+ struct nlattr *nest;
+
+ nest = ethnl_nest_start(skb, ETHA_SETTINGS_WOL);
+ if (!nest)
+ return -EMSGSIZE;
+ if (nla_put_bitfield32(skb, ETHA_WOL_MODES, wolinfo->wolopts,
+ wolinfo->supported))
+ goto err;
+ /* ioctl() restricts read access to wolinfo but the actual
+ * reason is to hide sopass from unprivileged users; netlink
+ * can show wol modes without sopass
+ */
+ if (privileged &&
+ nla_put(skb, ETHA_WOL_SOPASS, sizeof(wolinfo->sopass),
+ wolinfo->sopass))
+ goto err;
+ nla_nest_end(skb, nest);
+ return 0;
+
+err:
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+}
+
static int fill_settings(struct sk_buff *skb,
const struct common_req_info *req_info)
{
@@ -386,6 +443,11 @@ static int fill_settings(struct sk_buff *skb,
if (ret < 0)
return ret;
}
+ if (info_mask & ETH_SETTINGS_IM_WOL) {
+ ret = fill_wolinfo(skb, &data->wolinfo, data->privileged);
+ if (ret < 0)
+ return ret;
+ }

return 0;
}
@@ -431,6 +493,7 @@ static const struct nla_policy set_settings_policy[ETHA_SETTINGS_MAX + 1] = {
[ETHA_SETTINGS_LINK_INFO] = { .type = NLA_NESTED },
[ETHA_SETTINGS_LINK_MODES] = { .type = NLA_NESTED },
[ETHA_SETTINGS_LINK_STATE] = { .type = NLA_REJECT },
+ [ETHA_SETTINGS_WOL] = { .type = NLA_REJECT },
};

static int ethnl_set_link_ksettings(struct genl_info *info,
--
2.21.0