mirror of
https://github.com/torvalds/linux.git
synced 2024-12-05 10:32:35 +00:00
Merge branch 'netdev-support-dumping-a-single-netdev-in-qstats'
Jakub Kicinski says: ==================== netdev: support dumping a single netdev in qstats I was writing a test for page pool which depended on qstats, and got tired of having to filter dumps in user space. Add support for dumping stats for a single netdev. To get there we first need to add full support for extack in dumps (and fix a dump error handling bug in YNL, sent separately to the net tree). ==================== Link: https://lore.kernel.org/r/20240420023543.3300306-1-kuba@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
b2c8599f64
@ -486,6 +486,7 @@ operations:
|
||||
dump:
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
- scope
|
||||
reply:
|
||||
attributes:
|
||||
|
@ -70,6 +70,7 @@ static const struct nla_policy netdev_napi_get_dump_nl_policy[NETDEV_A_NAPI_IFIN
|
||||
|
||||
/* NETDEV_CMD_QSTATS_GET - dump */
|
||||
static const struct nla_policy netdev_qstats_get_nl_policy[NETDEV_A_QSTATS_SCOPE + 1] = {
|
||||
[NETDEV_A_QSTATS_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1),
|
||||
[NETDEV_A_QSTATS_SCOPE] = NLA_POLICY_MASK(NLA_UINT, 0x1),
|
||||
};
|
||||
|
||||
|
@ -639,6 +639,24 @@ nla_put_failure:
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
static int
|
||||
netdev_nl_qstats_get_dump_one(struct net_device *netdev, unsigned int scope,
|
||||
struct sk_buff *skb, const struct genl_info *info,
|
||||
struct netdev_nl_dump_ctx *ctx)
|
||||
{
|
||||
if (!netdev->stat_ops)
|
||||
return 0;
|
||||
|
||||
switch (scope) {
|
||||
case 0:
|
||||
return netdev_nl_stats_by_netdev(netdev, skb, info);
|
||||
case NETDEV_QSTATS_SCOPE_QUEUE:
|
||||
return netdev_nl_stats_by_queue(netdev, skb, info, ctx);
|
||||
}
|
||||
|
||||
return -EINVAL; /* Should not happen, per netlink policy */
|
||||
}
|
||||
|
||||
int netdev_nl_qstats_get_dumpit(struct sk_buff *skb,
|
||||
struct netlink_callback *cb)
|
||||
{
|
||||
@ -646,6 +664,7 @@ int netdev_nl_qstats_get_dumpit(struct sk_buff *skb,
|
||||
const struct genl_info *info = genl_info_dump(cb);
|
||||
struct net *net = sock_net(skb->sk);
|
||||
struct net_device *netdev;
|
||||
unsigned int ifindex;
|
||||
unsigned int scope;
|
||||
int err = 0;
|
||||
|
||||
@ -653,21 +672,28 @@ int netdev_nl_qstats_get_dumpit(struct sk_buff *skb,
|
||||
if (info->attrs[NETDEV_A_QSTATS_SCOPE])
|
||||
scope = nla_get_uint(info->attrs[NETDEV_A_QSTATS_SCOPE]);
|
||||
|
||||
rtnl_lock();
|
||||
for_each_netdev_dump(net, netdev, ctx->ifindex) {
|
||||
if (!netdev->stat_ops)
|
||||
continue;
|
||||
ifindex = 0;
|
||||
if (info->attrs[NETDEV_A_QSTATS_IFINDEX])
|
||||
ifindex = nla_get_u32(info->attrs[NETDEV_A_QSTATS_IFINDEX]);
|
||||
|
||||
switch (scope) {
|
||||
case 0:
|
||||
err = netdev_nl_stats_by_netdev(netdev, skb, info);
|
||||
break;
|
||||
case NETDEV_QSTATS_SCOPE_QUEUE:
|
||||
err = netdev_nl_stats_by_queue(netdev, skb, info, ctx);
|
||||
break;
|
||||
rtnl_lock();
|
||||
if (ifindex) {
|
||||
netdev = __dev_get_by_index(net, ifindex);
|
||||
if (netdev && netdev->stat_ops) {
|
||||
err = netdev_nl_qstats_get_dump_one(netdev, scope, skb,
|
||||
info, ctx);
|
||||
} else {
|
||||
NL_SET_BAD_ATTR(info->extack,
|
||||
info->attrs[NETDEV_A_QSTATS_IFINDEX]);
|
||||
err = netdev ? -EOPNOTSUPP : -ENODEV;
|
||||
}
|
||||
} else {
|
||||
for_each_netdev_dump(net, netdev, ctx->ifindex) {
|
||||
err = netdev_nl_qstats_get_dump_one(netdev, scope, skb,
|
||||
info, ctx);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
rtnl_unlock();
|
||||
|
||||
|
@ -2165,6 +2165,69 @@ __nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int type, int len, int fla
|
||||
}
|
||||
EXPORT_SYMBOL(__nlmsg_put);
|
||||
|
||||
static size_t
|
||||
netlink_ack_tlv_len(struct netlink_sock *nlk, int err,
|
||||
const struct netlink_ext_ack *extack)
|
||||
{
|
||||
size_t tlvlen;
|
||||
|
||||
if (!extack || !test_bit(NETLINK_F_EXT_ACK, &nlk->flags))
|
||||
return 0;
|
||||
|
||||
tlvlen = 0;
|
||||
if (extack->_msg)
|
||||
tlvlen += nla_total_size(strlen(extack->_msg) + 1);
|
||||
if (extack->cookie_len)
|
||||
tlvlen += nla_total_size(extack->cookie_len);
|
||||
|
||||
/* Following attributes are only reported as error (not warning) */
|
||||
if (!err)
|
||||
return tlvlen;
|
||||
|
||||
if (extack->bad_attr)
|
||||
tlvlen += nla_total_size(sizeof(u32));
|
||||
if (extack->policy)
|
||||
tlvlen += netlink_policy_dump_attr_size_estimate(extack->policy);
|
||||
if (extack->miss_type)
|
||||
tlvlen += nla_total_size(sizeof(u32));
|
||||
if (extack->miss_nest)
|
||||
tlvlen += nla_total_size(sizeof(u32));
|
||||
|
||||
return tlvlen;
|
||||
}
|
||||
|
||||
static void
|
||||
netlink_ack_tlv_fill(struct sk_buff *in_skb, struct sk_buff *skb,
|
||||
const struct nlmsghdr *nlh, int err,
|
||||
const struct netlink_ext_ack *extack)
|
||||
{
|
||||
if (extack->_msg)
|
||||
WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG, extack->_msg));
|
||||
if (extack->cookie_len)
|
||||
WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE,
|
||||
extack->cookie_len, extack->cookie));
|
||||
|
||||
if (!err)
|
||||
return;
|
||||
|
||||
if (extack->bad_attr &&
|
||||
!WARN_ON((u8 *)extack->bad_attr < in_skb->data ||
|
||||
(u8 *)extack->bad_attr >= in_skb->data + in_skb->len))
|
||||
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_OFFS,
|
||||
(u8 *)extack->bad_attr - (const u8 *)nlh));
|
||||
if (extack->policy)
|
||||
netlink_policy_dump_write_attr(skb, extack->policy,
|
||||
NLMSGERR_ATTR_POLICY);
|
||||
if (extack->miss_type)
|
||||
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_TYPE,
|
||||
extack->miss_type));
|
||||
if (extack->miss_nest &&
|
||||
!WARN_ON((u8 *)extack->miss_nest < in_skb->data ||
|
||||
(u8 *)extack->miss_nest > in_skb->data + in_skb->len))
|
||||
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_NEST,
|
||||
(u8 *)extack->miss_nest - (const u8 *)nlh));
|
||||
}
|
||||
|
||||
/*
|
||||
* It looks a bit ugly.
|
||||
* It would be better to create kernel thread.
|
||||
@ -2175,6 +2238,7 @@ static int netlink_dump_done(struct netlink_sock *nlk, struct sk_buff *skb,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct nlmsghdr *nlh;
|
||||
size_t extack_len;
|
||||
|
||||
nlh = nlmsg_put_answer(skb, cb, NLMSG_DONE, sizeof(nlk->dump_done_errno),
|
||||
NLM_F_MULTI | cb->answer_flags);
|
||||
@ -2184,10 +2248,14 @@ static int netlink_dump_done(struct netlink_sock *nlk, struct sk_buff *skb,
|
||||
nl_dump_check_consistent(cb, nlh);
|
||||
memcpy(nlmsg_data(nlh), &nlk->dump_done_errno, sizeof(nlk->dump_done_errno));
|
||||
|
||||
if (extack->_msg && test_bit(NETLINK_F_EXT_ACK, &nlk->flags)) {
|
||||
extack_len = netlink_ack_tlv_len(nlk, nlk->dump_done_errno, extack);
|
||||
if (extack_len) {
|
||||
nlh->nlmsg_flags |= NLM_F_ACK_TLVS;
|
||||
if (!nla_put_string(skb, NLMSGERR_ATTR_MSG, extack->_msg))
|
||||
if (skb_tailroom(skb) >= extack_len) {
|
||||
netlink_ack_tlv_fill(cb->skb, skb, cb->nlh,
|
||||
nlk->dump_done_errno, extack);
|
||||
nlmsg_end(skb, nlh);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -2406,69 +2474,6 @@ error_free:
|
||||
}
|
||||
EXPORT_SYMBOL(__netlink_dump_start);
|
||||
|
||||
static size_t
|
||||
netlink_ack_tlv_len(struct netlink_sock *nlk, int err,
|
||||
const struct netlink_ext_ack *extack)
|
||||
{
|
||||
size_t tlvlen;
|
||||
|
||||
if (!extack || !test_bit(NETLINK_F_EXT_ACK, &nlk->flags))
|
||||
return 0;
|
||||
|
||||
tlvlen = 0;
|
||||
if (extack->_msg)
|
||||
tlvlen += nla_total_size(strlen(extack->_msg) + 1);
|
||||
if (extack->cookie_len)
|
||||
tlvlen += nla_total_size(extack->cookie_len);
|
||||
|
||||
/* Following attributes are only reported as error (not warning) */
|
||||
if (!err)
|
||||
return tlvlen;
|
||||
|
||||
if (extack->bad_attr)
|
||||
tlvlen += nla_total_size(sizeof(u32));
|
||||
if (extack->policy)
|
||||
tlvlen += netlink_policy_dump_attr_size_estimate(extack->policy);
|
||||
if (extack->miss_type)
|
||||
tlvlen += nla_total_size(sizeof(u32));
|
||||
if (extack->miss_nest)
|
||||
tlvlen += nla_total_size(sizeof(u32));
|
||||
|
||||
return tlvlen;
|
||||
}
|
||||
|
||||
static void
|
||||
netlink_ack_tlv_fill(struct sk_buff *in_skb, struct sk_buff *skb,
|
||||
struct nlmsghdr *nlh, int err,
|
||||
const struct netlink_ext_ack *extack)
|
||||
{
|
||||
if (extack->_msg)
|
||||
WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG, extack->_msg));
|
||||
if (extack->cookie_len)
|
||||
WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE,
|
||||
extack->cookie_len, extack->cookie));
|
||||
|
||||
if (!err)
|
||||
return;
|
||||
|
||||
if (extack->bad_attr &&
|
||||
!WARN_ON((u8 *)extack->bad_attr < in_skb->data ||
|
||||
(u8 *)extack->bad_attr >= in_skb->data + in_skb->len))
|
||||
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_OFFS,
|
||||
(u8 *)extack->bad_attr - (u8 *)nlh));
|
||||
if (extack->policy)
|
||||
netlink_policy_dump_write_attr(skb, extack->policy,
|
||||
NLMSGERR_ATTR_POLICY);
|
||||
if (extack->miss_type)
|
||||
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_TYPE,
|
||||
extack->miss_type));
|
||||
if (extack->miss_nest &&
|
||||
!WARN_ON((u8 *)extack->miss_nest < in_skb->data ||
|
||||
(u8 *)extack->miss_nest > in_skb->data + in_skb->len))
|
||||
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_NEST,
|
||||
(u8 *)extack->miss_nest - (u8 *)nlh));
|
||||
}
|
||||
|
||||
void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err,
|
||||
const struct netlink_ext_ack *extack)
|
||||
{
|
||||
|
@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
from lib.py import ksft_run, ksft_exit
|
||||
from lib.py import ksft_in, ksft_true, KsftSkipEx, KsftXfailEx
|
||||
from lib.py import ksft_run, ksft_exit, ksft_pr
|
||||
from lib.py import ksft_ge, ksft_eq, ksft_in, ksft_true, ksft_raises, KsftSkipEx, KsftXfailEx
|
||||
from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
|
||||
from lib.py import NetDrvEnv
|
||||
|
||||
@ -77,9 +77,65 @@ def pkt_byte_sum(cfg) -> None:
|
||||
raise Exception("Qstats are lower, fetched later")
|
||||
|
||||
|
||||
def qstat_by_ifindex(cfg) -> None:
|
||||
global netfam
|
||||
global rtnl
|
||||
|
||||
# Construct a map ifindex -> [dump, by-index, dump]
|
||||
ifindexes = {}
|
||||
stats = netfam.qstats_get({}, dump=True)
|
||||
for entry in stats:
|
||||
ifindexes[entry['ifindex']] = [entry, None, None]
|
||||
|
||||
for ifindex in ifindexes.keys():
|
||||
entry = netfam.qstats_get({"ifindex": ifindex}, dump=True)
|
||||
ksft_eq(len(entry), 1)
|
||||
ifindexes[entry[0]['ifindex']][1] = entry[0]
|
||||
|
||||
stats = netfam.qstats_get({}, dump=True)
|
||||
for entry in stats:
|
||||
ifindexes[entry['ifindex']][2] = entry
|
||||
|
||||
if len(ifindexes) == 0:
|
||||
raise KsftSkipEx("No ifindex supports qstats")
|
||||
|
||||
# Now make sure the stats match/make sense
|
||||
for ifindex, triple in ifindexes.items():
|
||||
all_keys = triple[0].keys() | triple[1].keys() | triple[2].keys()
|
||||
|
||||
for key in all_keys:
|
||||
ksft_ge(triple[1][key], triple[0][key], comment="bad key: " + key)
|
||||
ksft_ge(triple[2][key], triple[1][key], comment="bad key: " + key)
|
||||
|
||||
# Test invalid dumps
|
||||
# 0 is invalid
|
||||
with ksft_raises(NlError) as cm:
|
||||
netfam.qstats_get({"ifindex": 0}, dump=True)
|
||||
ksft_eq(cm.exception.nl_msg.error, -34)
|
||||
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
|
||||
|
||||
# loopback has no stats
|
||||
with ksft_raises(NlError) as cm:
|
||||
netfam.qstats_get({"ifindex": 1}, dump=True)
|
||||
ksft_eq(cm.exception.nl_msg.error, -95)
|
||||
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
|
||||
|
||||
# Try to get stats for lowest unused ifindex but not 0
|
||||
devs = rtnl.getlink({}, dump=True)
|
||||
all_ifindexes = set([dev["ifi-index"] for dev in devs])
|
||||
lowest = 2
|
||||
while lowest in all_ifindexes:
|
||||
lowest += 1
|
||||
|
||||
with ksft_raises(NlError) as cm:
|
||||
netfam.qstats_get({"ifindex": lowest}, dump=True)
|
||||
ksft_eq(cm.exception.nl_msg.error, -19)
|
||||
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with NetDrvEnv(__file__) as cfg:
|
||||
ksft_run([check_pause, check_fec, pkt_byte_sum],
|
||||
ksft_run([check_pause, check_fec, pkt_byte_sum, qstat_by_ifindex],
|
||||
args=(cfg, ))
|
||||
ksft_exit()
|
||||
|
||||
|
@ -53,6 +53,24 @@ def ksft_ge(a, b, comment=""):
|
||||
_fail("Check failed", a, "<", b, comment)
|
||||
|
||||
|
||||
class ksft_raises:
|
||||
def __init__(self, expected_type):
|
||||
self.exception = None
|
||||
self.expected_type = expected_type
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if exc_type is None:
|
||||
_fail(f"Expected exception {str(self.expected_type.__name__)}, none raised")
|
||||
elif self.expected_type != exc_type:
|
||||
_fail(f"Expected exception {str(self.expected_type.__name__)}, raised {str(exc_type.__name__)}")
|
||||
self.exception = exc_val
|
||||
# Suppress the exception if its the expected one
|
||||
return self.expected_type == exc_type
|
||||
|
||||
|
||||
def ksft_busy_wait(cond, sleep=0.005, deadline=1, comment=""):
|
||||
end = time.monotonic() + deadline
|
||||
while True:
|
||||
|
Loading…
Reference in New Issue
Block a user