Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+188 -81
+1
Documentation/netlink/specs/netdev.yaml
··· 486 486 dump: 487 487 request: 488 488 attributes: 489 + - ifindex 489 490 - scope 490 491 reply: 491 492 attributes:
+1
net/core/netdev-genl-gen.c
··· 70 70 71 71 /* NETDEV_CMD_QSTATS_GET - dump */ 72 72 static const struct nla_policy netdev_qstats_get_nl_policy[NETDEV_A_QSTATS_SCOPE + 1] = { 73 + [NETDEV_A_QSTATS_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), 73 74 [NETDEV_A_QSTATS_SCOPE] = NLA_POLICY_MASK(NLA_UINT, 0x1), 74 75 }; 75 76
+39 -13
net/core/netdev-genl.c
··· 639 639 return -EMSGSIZE; 640 640 } 641 641 642 + static int 643 + netdev_nl_qstats_get_dump_one(struct net_device *netdev, unsigned int scope, 644 + struct sk_buff *skb, const struct genl_info *info, 645 + struct netdev_nl_dump_ctx *ctx) 646 + { 647 + if (!netdev->stat_ops) 648 + return 0; 649 + 650 + switch (scope) { 651 + case 0: 652 + return netdev_nl_stats_by_netdev(netdev, skb, info); 653 + case NETDEV_QSTATS_SCOPE_QUEUE: 654 + return netdev_nl_stats_by_queue(netdev, skb, info, ctx); 655 + } 656 + 657 + return -EINVAL; /* Should not happen, per netlink policy */ 658 + } 659 + 642 660 int netdev_nl_qstats_get_dumpit(struct sk_buff *skb, 643 661 struct netlink_callback *cb) 644 662 { ··· 664 646 const struct genl_info *info = genl_info_dump(cb); 665 647 struct net *net = sock_net(skb->sk); 666 648 struct net_device *netdev; 649 + unsigned int ifindex; 667 650 unsigned int scope; 668 651 int err = 0; 669 652 ··· 672 653 if (info->attrs[NETDEV_A_QSTATS_SCOPE]) 673 654 scope = nla_get_uint(info->attrs[NETDEV_A_QSTATS_SCOPE]); 674 655 675 - rtnl_lock(); 676 - for_each_netdev_dump(net, netdev, ctx->ifindex) { 677 - if (!netdev->stat_ops) 678 - continue; 656 + ifindex = 0; 657 + if (info->attrs[NETDEV_A_QSTATS_IFINDEX]) 658 + ifindex = nla_get_u32(info->attrs[NETDEV_A_QSTATS_IFINDEX]); 679 659 680 - switch (scope) { 681 - case 0: 682 - err = netdev_nl_stats_by_netdev(netdev, skb, info); 683 - break; 684 - case NETDEV_QSTATS_SCOPE_QUEUE: 685 - err = netdev_nl_stats_by_queue(netdev, skb, info, ctx); 686 - break; 660 + rtnl_lock(); 661 + if (ifindex) { 662 + netdev = __dev_get_by_index(net, ifindex); 663 + if (netdev && netdev->stat_ops) { 664 + err = netdev_nl_qstats_get_dump_one(netdev, scope, skb, 665 + info, ctx); 666 + } else { 667 + NL_SET_BAD_ATTR(info->extack, 668 + info->attrs[NETDEV_A_QSTATS_IFINDEX]); 669 + err = netdev ? -EOPNOTSUPP : -ENODEV; 687 670 } 688 - if (err < 0) 689 - break; 671 + } else { 672 + for_each_netdev_dump(net, netdev, ctx->ifindex) { 673 + err = netdev_nl_qstats_get_dump_one(netdev, scope, skb, 674 + info, ctx); 675 + if (err < 0) 676 + break; 677 + } 690 678 } 691 679 rtnl_unlock(); 692 680
+70 -65
net/netlink/af_netlink.c
··· 2165 2165 } 2166 2166 EXPORT_SYMBOL(__nlmsg_put); 2167 2167 2168 + static size_t 2169 + netlink_ack_tlv_len(struct netlink_sock *nlk, int err, 2170 + const struct netlink_ext_ack *extack) 2171 + { 2172 + size_t tlvlen; 2173 + 2174 + if (!extack || !test_bit(NETLINK_F_EXT_ACK, &nlk->flags)) 2175 + return 0; 2176 + 2177 + tlvlen = 0; 2178 + if (extack->_msg) 2179 + tlvlen += nla_total_size(strlen(extack->_msg) + 1); 2180 + if (extack->cookie_len) 2181 + tlvlen += nla_total_size(extack->cookie_len); 2182 + 2183 + /* Following attributes are only reported as error (not warning) */ 2184 + if (!err) 2185 + return tlvlen; 2186 + 2187 + if (extack->bad_attr) 2188 + tlvlen += nla_total_size(sizeof(u32)); 2189 + if (extack->policy) 2190 + tlvlen += netlink_policy_dump_attr_size_estimate(extack->policy); 2191 + if (extack->miss_type) 2192 + tlvlen += nla_total_size(sizeof(u32)); 2193 + if (extack->miss_nest) 2194 + tlvlen += nla_total_size(sizeof(u32)); 2195 + 2196 + return tlvlen; 2197 + } 2198 + 2199 + static void 2200 + netlink_ack_tlv_fill(struct sk_buff *in_skb, struct sk_buff *skb, 2201 + const struct nlmsghdr *nlh, int err, 2202 + const struct netlink_ext_ack *extack) 2203 + { 2204 + if (extack->_msg) 2205 + WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG, extack->_msg)); 2206 + if (extack->cookie_len) 2207 + WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE, 2208 + extack->cookie_len, extack->cookie)); 2209 + 2210 + if (!err) 2211 + return; 2212 + 2213 + if (extack->bad_attr && 2214 + !WARN_ON((u8 *)extack->bad_attr < in_skb->data || 2215 + (u8 *)extack->bad_attr >= in_skb->data + in_skb->len)) 2216 + WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_OFFS, 2217 + (u8 *)extack->bad_attr - (const u8 *)nlh)); 2218 + if (extack->policy) 2219 + netlink_policy_dump_write_attr(skb, extack->policy, 2220 + NLMSGERR_ATTR_POLICY); 2221 + if (extack->miss_type) 2222 + WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_TYPE, 2223 + extack->miss_type)); 2224 + if (extack->miss_nest && 2225 + !WARN_ON((u8 *)extack->miss_nest < in_skb->data || 2226 + (u8 *)extack->miss_nest > in_skb->data + in_skb->len)) 2227 + WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_NEST, 2228 + (u8 *)extack->miss_nest - (const u8 *)nlh)); 2229 + } 2230 + 2168 2231 /* 2169 2232 * It looks a bit ugly. 2170 2233 * It would be better to create kernel thread. ··· 2238 2175 struct netlink_ext_ack *extack) 2239 2176 { 2240 2177 struct nlmsghdr *nlh; 2178 + size_t extack_len; 2241 2179 2242 2180 nlh = nlmsg_put_answer(skb, cb, NLMSG_DONE, sizeof(nlk->dump_done_errno), 2243 2181 NLM_F_MULTI | cb->answer_flags); ··· 2248 2184 nl_dump_check_consistent(cb, nlh); 2249 2185 memcpy(nlmsg_data(nlh), &nlk->dump_done_errno, sizeof(nlk->dump_done_errno)); 2250 2186 2251 - if (extack->_msg && test_bit(NETLINK_F_EXT_ACK, &nlk->flags)) { 2187 + extack_len = netlink_ack_tlv_len(nlk, nlk->dump_done_errno, extack); 2188 + if (extack_len) { 2252 2189 nlh->nlmsg_flags |= NLM_F_ACK_TLVS; 2253 - if (!nla_put_string(skb, NLMSGERR_ATTR_MSG, extack->_msg)) 2190 + if (skb_tailroom(skb) >= extack_len) { 2191 + netlink_ack_tlv_fill(cb->skb, skb, cb->nlh, 2192 + nlk->dump_done_errno, extack); 2254 2193 nlmsg_end(skb, nlh); 2194 + } 2255 2195 } 2256 2196 2257 2197 return 0; ··· 2473 2405 return ret; 2474 2406 } 2475 2407 EXPORT_SYMBOL(__netlink_dump_start); 2476 - 2477 - static size_t 2478 - netlink_ack_tlv_len(struct netlink_sock *nlk, int err, 2479 - const struct netlink_ext_ack *extack) 2480 - { 2481 - size_t tlvlen; 2482 - 2483 - if (!extack || !test_bit(NETLINK_F_EXT_ACK, &nlk->flags)) 2484 - return 0; 2485 - 2486 - tlvlen = 0; 2487 - if (extack->_msg) 2488 - tlvlen += nla_total_size(strlen(extack->_msg) + 1); 2489 - if (extack->cookie_len) 2490 - tlvlen += nla_total_size(extack->cookie_len); 2491 - 2492 - /* Following attributes are only reported as error (not warning) */ 2493 - if (!err) 2494 - return tlvlen; 2495 - 2496 - if (extack->bad_attr) 2497 - tlvlen += nla_total_size(sizeof(u32)); 2498 - if (extack->policy) 2499 - tlvlen += netlink_policy_dump_attr_size_estimate(extack->policy); 2500 - if (extack->miss_type) 2501 - tlvlen += nla_total_size(sizeof(u32)); 2502 - if (extack->miss_nest) 2503 - tlvlen += nla_total_size(sizeof(u32)); 2504 - 2505 - return tlvlen; 2506 - } 2507 - 2508 - static void 2509 - netlink_ack_tlv_fill(struct sk_buff *in_skb, struct sk_buff *skb, 2510 - struct nlmsghdr *nlh, int err, 2511 - const struct netlink_ext_ack *extack) 2512 - { 2513 - if (extack->_msg) 2514 - WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG, extack->_msg)); 2515 - if (extack->cookie_len) 2516 - WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE, 2517 - extack->cookie_len, extack->cookie)); 2518 - 2519 - if (!err) 2520 - return; 2521 - 2522 - if (extack->bad_attr && 2523 - !WARN_ON((u8 *)extack->bad_attr < in_skb->data || 2524 - (u8 *)extack->bad_attr >= in_skb->data + in_skb->len)) 2525 - WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_OFFS, 2526 - (u8 *)extack->bad_attr - (u8 *)nlh)); 2527 - if (extack->policy) 2528 - netlink_policy_dump_write_attr(skb, extack->policy, 2529 - NLMSGERR_ATTR_POLICY); 2530 - if (extack->miss_type) 2531 - WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_TYPE, 2532 - extack->miss_type)); 2533 - if (extack->miss_nest && 2534 - !WARN_ON((u8 *)extack->miss_nest < in_skb->data || 2535 - (u8 *)extack->miss_nest > in_skb->data + in_skb->len)) 2536 - WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_NEST, 2537 - (u8 *)extack->miss_nest - (u8 *)nlh)); 2538 - } 2539 2408 2540 2409 void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err, 2541 2410 const struct netlink_ext_ack *extack)
+59 -3
tools/testing/selftests/drivers/net/stats.py
··· 1 1 #!/usr/bin/env python3 2 2 # SPDX-License-Identifier: GPL-2.0 3 3 4 - from lib.py import ksft_run, ksft_exit 5 - from lib.py import ksft_in, ksft_true, KsftSkipEx, KsftXfailEx 4 + from lib.py import ksft_run, ksft_exit, ksft_pr 5 + from lib.py import ksft_ge, ksft_eq, ksft_in, ksft_true, ksft_raises, KsftSkipEx, KsftXfailEx 6 6 from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError 7 7 from lib.py import NetDrvEnv 8 8 ··· 77 77 raise Exception("Qstats are lower, fetched later") 78 78 79 79 80 + def qstat_by_ifindex(cfg) -> None: 81 + global netfam 82 + global rtnl 83 + 84 + # Construct a map ifindex -> [dump, by-index, dump] 85 + ifindexes = {} 86 + stats = netfam.qstats_get({}, dump=True) 87 + for entry in stats: 88 + ifindexes[entry['ifindex']] = [entry, None, None] 89 + 90 + for ifindex in ifindexes.keys(): 91 + entry = netfam.qstats_get({"ifindex": ifindex}, dump=True) 92 + ksft_eq(len(entry), 1) 93 + ifindexes[entry[0]['ifindex']][1] = entry[0] 94 + 95 + stats = netfam.qstats_get({}, dump=True) 96 + for entry in stats: 97 + ifindexes[entry['ifindex']][2] = entry 98 + 99 + if len(ifindexes) == 0: 100 + raise KsftSkipEx("No ifindex supports qstats") 101 + 102 + # Now make sure the stats match/make sense 103 + for ifindex, triple in ifindexes.items(): 104 + all_keys = triple[0].keys() | triple[1].keys() | triple[2].keys() 105 + 106 + for key in all_keys: 107 + ksft_ge(triple[1][key], triple[0][key], comment="bad key: " + key) 108 + ksft_ge(triple[2][key], triple[1][key], comment="bad key: " + key) 109 + 110 + # Test invalid dumps 111 + # 0 is invalid 112 + with ksft_raises(NlError) as cm: 113 + netfam.qstats_get({"ifindex": 0}, dump=True) 114 + ksft_eq(cm.exception.nl_msg.error, -34) 115 + ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex') 116 + 117 + # loopback has no stats 118 + with ksft_raises(NlError) as cm: 119 + netfam.qstats_get({"ifindex": 1}, dump=True) 120 + ksft_eq(cm.exception.nl_msg.error, -95) 121 + ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex') 122 + 123 + # Try to get stats for lowest unused ifindex but not 0 124 + devs = rtnl.getlink({}, dump=True) 125 + all_ifindexes = set([dev["ifi-index"] for dev in devs]) 126 + lowest = 2 127 + while lowest in all_ifindexes: 128 + lowest += 1 129 + 130 + with ksft_raises(NlError) as cm: 131 + netfam.qstats_get({"ifindex": lowest}, dump=True) 132 + ksft_eq(cm.exception.nl_msg.error, -19) 133 + ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex') 134 + 135 + 80 136 def main() -> None: 81 137 with NetDrvEnv(__file__) as cfg: 82 - ksft_run([check_pause, check_fec, pkt_byte_sum], 138 + ksft_run([check_pause, check_fec, pkt_byte_sum, qstat_by_ifindex], 83 139 args=(cfg, )) 84 140 ksft_exit() 85 141
+18
tools/testing/selftests/net/lib/py/ksft.py
··· 53 53 _fail("Check failed", a, "<", b, comment) 54 54 55 55 56 + class ksft_raises: 57 + def __init__(self, expected_type): 58 + self.exception = None 59 + self.expected_type = expected_type 60 + 61 + def __enter__(self): 62 + return self 63 + 64 + def __exit__(self, exc_type, exc_val, exc_tb): 65 + if exc_type is None: 66 + _fail(f"Expected exception {str(self.expected_type.__name__)}, none raised") 67 + elif self.expected_type != exc_type: 68 + _fail(f"Expected exception {str(self.expected_type.__name__)}, raised {str(exc_type.__name__)}") 69 + self.exception = exc_val 70 + # Suppress the exception if its the expected one 71 + return self.expected_type == exc_type 72 + 73 + 56 74 def ksft_busy_wait(cond, sleep=0.005, deadline=1, comment=""): 57 75 end = time.monotonic() + deadline 58 76 while True: