// SPDX-License-Identifier: GPL-2.0-only
#include <net/sock.h>
#include <linux/ethtool_netlink.h>
#include <linux/pm_runtime.h>
#include "netlink.h"
static struct genl_family ethtool_genl_family;
static bool ethnl_ok __read_mostly;
static u32 ethnl_bcast_seq;
#define ETHTOOL_FLAGS_BASIC (ETHTOOL_FLAG_COMPACT_BITSETS | \
ETHTOOL_FLAG_OMIT_REPLY)
#define ETHTOOL_FLAGS_STATS (ETHTOOL_FLAGS_BASIC | ETHTOOL_FLAG_STATS)
const struct nla_policy ethnl_header_policy[] = {
[ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 },
[ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING,
.len = ALTIFNAMSIZ - 1 },
[ETHTOOL_A_HEADER_FLAGS] = NLA_POLICY_MASK(NLA_U32,
ETHTOOL_FLAGS_BASIC),
};
const struct nla_policy ethnl_header_policy_stats[] = {
[ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 },
[ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING,
.len = ALTIFNAMSIZ - 1 },
[ETHTOOL_A_HEADER_FLAGS] = NLA_POLICY_MASK(NLA_U32,
ETHTOOL_FLAGS_STATS),
};
int ethnl_ops_begin(struct net_device *dev)
{
int ret;
if (!dev)
return -ENODEV;
if (dev->dev.parent)
pm_runtime_get_sync(dev->dev.parent);
if (!netif_device_present(dev) ||
dev->reg_state == NETREG_UNREGISTERING) {
ret = -ENODEV;
goto err;
}
if (dev->ethtool_ops->begin) {
ret = dev->ethtool_ops->begin(dev);
if (ret)
goto err;
}
return 0;
err:
if (dev->dev.parent)
pm_runtime_put(dev->dev.parent);
return ret;
}
void ethnl_ops_complete(struct net_device *dev)
{
if (dev->ethtool_ops->complete)
dev->ethtool_ops->complete(dev);
if (dev->dev.parent)
pm_runtime_put(dev->dev.parent);
}
/**
* ethnl_parse_header_dev_get() - parse request header
* @req_info: structure to put results into
* @header: nest attribute with request header
* @net: request netns
* @extack: netlink extack for error reporting
* @require_dev: fail if no device identified in header
*
* Parse request header in nested attribute @nest and puts results into
* the structure pointed to by @req_info. Extack from @info is used for error
* reporting. If req_info->dev is not null on return, reference to it has
* been taken. If error is returned, *req_info is null initialized and no
* reference is held.
*
* Return: 0 on success or negative error code
*/
int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
const struct nlattr *header, struct net *net,
struct netlink_ext_ack *extack, bool require_dev)
{
struct nlattr *tb[ARRAY_SIZE(ethnl_header_policy)];
const struct nlattr *devname_attr;
struct net_device *dev = NULL;
u32 flags = 0;
int ret;
if (!header) {
if (!require_dev)
return 0;
NL_SET_ERR_MSG(extack, "request header missing");
return -EINVAL;
}
/* No validation here, command policy should have a nested policy set
* for the header, therefore validation should have already been done.
*/
ret = nla_parse_nested(tb, ARRAY_SIZE(ethnl_header_policy) - 1, header,
NULL, extack);
if (ret < 0)
return ret;
if (tb[ETHTOOL_A_HEADER_FLAGS])
flags = nla_get_u32(tb[ETHTOOL_A_HEADER_FLAGS]);
devname_attr = tb[ETHTOOL_A_HEADER_DEV_NAME];
if (tb[ETHTOOL_A_HEADER_DEV_INDEX]) {
u32 ifindex = nla_get_u32(tb[ETHTOOL_A_HEADER_DEV_INDEX]);
dev = netdev_get_by_index(net, ifindex, &req_info->dev_tracker,
GFP_KERNEL);
if (!dev) {
NL_SET_ERR_MSG_ATTR(extack,
tb[ETHTOOL_A_HEADER_DEV_INDEX],
"no device matches ifindex");
return -ENODEV;
}
/* if both ifindex and ifname are passed, they must match */
if (devname_attr &&
strncmp(dev->name, nla_data(devname_attr), IFNAMSIZ)) {
netdev_put(dev, &req_info->dev_tracker);
NL_SET_ERR_MSG_ATTR(extack, header,
"ifindex and name do not match");
return -ENODEV;
}
} else if (devname_attr) {
dev = netdev_get_by_name(net, nla_data(devname_attr),
&req_info->dev_tracker, GFP_KERNEL);
if (!dev) {