From: Mohsin Bashir <mohsin.bashr@gmail.com>
To: netdev@vger.kernel.org
Cc: dsahern@kernel.org, stephen@networkplumber.org,
pabeni@redhat.com, kuba@kernel.org, ernis@linux.microsoft.com,
mohsin.bashr@gmail.com, alexanderduyck@gmail.com,
Claude Assistant <noreply@anthropic.com>
Subject: [PATCH iproute2-next v3 5/6] netshaper: Add group command for creating scheduling hierarchies
Date: Mon, 11 May 2026 11:39:14 -0700 [thread overview]
Message-ID: <20260511183915.797792-6-mohsin.bashr@gmail.com> (raw)
In-Reply-To: <20260511183915.797792-1-mohsin.bashr@gmail.com>
From: Mohsin Bashir <hmohsin@meta.com>
Add the group command to create and update scheduling groups via the
NET_SHAPER_CMD_GROUP netlink operation. This enables building shaper
hierarchies by specifying a node handle, parent scope, rate parameters,
and a set of queue leaf shapers to attach.
The group command allows the node handle id to be omitted, letting
the kernel auto-assign one for new nodes. Only queue scope is
accepted for leaf shapers. Group handle and parent scopes are
validated to accept only node or netdev. Parent node scope
requires an explicit id since it must reference an existing node.
Common argument parsing (dev, bw-min, bw-max, weight) reuses the
parse_shaper_arg() helper introduced in the previous patch.
Example usage:
netshaper group dev foo handle scope node parent scope netdev \
bw-max 1gbit leaves scope queue id 0 scope queue id 1
Reviewed-by: Claude Assistant <noreply@anthropic.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
netshaper/netshaper.c | 220 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 219 insertions(+), 1 deletion(-)
diff --git a/netshaper/netshaper.c b/netshaper/netshaper.c
index 9ccd09f4..4519eb5d 100644
--- a/netshaper/netshaper.c
+++ b/netshaper/netshaper.c
@@ -31,10 +31,15 @@ static void usage(void)
fprintf(stderr,
"Usage: netshaper [ OPTIONS ] { COMMAND | help }\n"
"OPTIONS := { -V[ersion] | -c[olor] | -help }\n"
- "COMMAND := { set | get | delete } dev DEVNAME\n"
+ "COMMAND := { set | get | delete | group } dev DEVNAME\n"
" handle scope HANDLE_SCOPE [id HANDLE_ID]\n"
" [bw-min BW_MIN] [bw-max BW_MAX] [weight WEIGHT]\n"
"\n"
+ "netshaper group dev DEVNAME handle scope SCOPE [ id ID ]\n"
+ " parent scope SCOPE [ id ID ]\n"
+ " [ bw-min BW ] [ bw-max BW ] [ weight WEIGHT ]\n"
+ " leaves { scope SCOPE id ID } [ ... ]\n"
+ "\n"
"Where: DEVNAME := STRING\n"
" HANDLE_SCOPE := { netdev | queue | node }\n"
" HANDLE_ID := UINT (required for queue/node, optional for netdev)\n"
@@ -319,6 +324,217 @@ static int do_cmd(int argc, char **argv, int cmd)
return err;
}
+#define NET_SHAPER_MAX_LEAVES 256
+
+static int do_group(int argc, char **argv)
+{
+ GENL_REQUEST(req, 4096, genl_family, 0, NET_SHAPER_FAMILY_VERSION,
+ NET_SHAPER_CMD_GROUP, NLM_F_REQUEST | NLM_F_ACK);
+
+ struct shaper_args args = SHAPER_ARGS_INIT;
+ bool parsing_leaves = false, has_handle_id = false, has_parent_id = false;
+ int parent_scope = -1, num_leaves = 0;
+ int err, ret, handle_scope = NET_SHAPER_SCOPE_UNSPEC;
+ __u32 handle_id = 0, parent_id = 0;
+ struct nlmsghdr *answer;
+
+ struct {
+ int scope;
+ __u32 id;
+ } leaves[NET_SHAPER_MAX_LEAVES];
+
+ while (argc > 0) {
+ if (parsing_leaves) {
+ if (strcmp(*argv, "scope") == 0) {
+ int lscope;
+
+ NEXT_ARG();
+ lscope = parse_scope(*argv);
+ if (lscope < 0) {
+ fprintf(stderr, "Invalid leaf scope \"%s\"\n",
+ *argv);
+ return -1;
+ }
+ NEXT_ARG();
+ if (strcmp(*argv, "id") != 0) {
+ fprintf(stderr, "Expected \"id\" after leaf scope\n");
+ return -1;
+ }
+
+ NEXT_ARG();
+ if (num_leaves >= ARRAY_SIZE(leaves)) {
+ fprintf(stderr, "Too many leaves\n");
+ return -1;
+ }
+ leaves[num_leaves].scope = lscope;
+ if (get_unsigned(&leaves[num_leaves].id, *argv, 10)) {
+ fprintf(stderr, "Invalid leaf id\n");
+ return -1;
+ }
+ num_leaves++;
+ argc--;
+ argv++;
+ continue;
+ }
+ parsing_leaves = false;
+ }
+
+ ret = parse_shaper_arg(*argv, &argc, &argv, &args);
+ if (ret < 0)
+ return -1;
+ if (ret > 0) {
+ argc--;
+ argv++;
+ continue;
+ }
+
+ if (strcmp(*argv, "handle") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "scope") != 0) {
+ fprintf(stderr, "Expected \"scope\" after \"handle\"\n");
+ return -1;
+ }
+ NEXT_ARG();
+ handle_scope = parse_scope(*argv);
+ if (handle_scope < 0) {
+ fprintf(stderr, "Invalid handle scope \"%s\"\n",
+ *argv);
+ return -1;
+ }
+ if (handle_scope != NET_SHAPER_SCOPE_NODE &&
+ handle_scope != NET_SHAPER_SCOPE_NETDEV) {
+ fprintf(stderr, "Group handle scope must be \"node\" or \"netdev\"\n");
+ return -1;
+ }
+
+ if (argc > 1 && strcmp(argv[1], "id") == 0) {
+ NEXT_ARG();
+ NEXT_ARG();
+ if (get_unsigned(&handle_id, *argv, 10)) {
+ fprintf(stderr, "Invalid handle id\n");
+ return -1;
+ }
+ has_handle_id = true;
+ }
+ } else if (strcmp(*argv, "parent") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "scope") != 0) {
+ fprintf(stderr, "Expected \"scope\" after \"parent\"\n");
+ return -1;
+ }
+ NEXT_ARG();
+ parent_scope = parse_scope(*argv);
+ if (parent_scope < 0) {
+ fprintf(stderr, "Invalid parent scope \"%s\"\n",
+ *argv);
+ return -1;
+ }
+ if (parent_scope != NET_SHAPER_SCOPE_NODE &&
+ parent_scope != NET_SHAPER_SCOPE_NETDEV) {
+ fprintf(stderr, "Parent scope must be \"node\" or \"netdev\"\n");
+ return -1;
+ }
+
+ if (parent_scope == NET_SHAPER_SCOPE_NODE) {
+ NEXT_ARG();
+ if (strcmp(*argv, "id") != 0) {
+ fprintf(stderr, "What is \"%s\"\n", *argv);
+ usage();
+ return -1;
+ }
+ NEXT_ARG();
+ if (get_unsigned(&parent_id, *argv, 10)) {
+ fprintf(stderr, "Invalid parent id\n");
+ return -1;
+ }
+ has_parent_id = true;
+ } else if (argc > 1 && strcmp(argv[1], "id") == 0) {
+ NEXT_ARG();
+ NEXT_ARG();
+ if (get_unsigned(&parent_id, *argv, 10)) {
+ fprintf(stderr, "Invalid parent id\n");
+ return -1;
+ }
+ has_parent_id = true;
+ }
+ } else if (strcmp(*argv, "leaves") == 0) {
+ parsing_leaves = true;
+ argc--;
+ argv++;
+ continue;
+ } else {
+ fprintf(stderr, "What is \"%s\"\n", *argv);
+ usage();
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+
+ if (args.ifindex == -1)
+ missarg("dev");
+ if (handle_scope == NET_SHAPER_SCOPE_UNSPEC)
+ missarg("handle");
+ if (parent_scope < 0)
+ missarg("parent");
+ if (num_leaves == 0)
+ missarg("leaves");
+
+ addattr32(&req.n, sizeof(req), NET_SHAPER_A_IFINDEX, args.ifindex);
+
+ struct rtattr *parent = addattr_nest(&req.n, sizeof(req),
+ NET_SHAPER_A_PARENT | NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), NET_SHAPER_A_HANDLE_SCOPE, parent_scope);
+ if (has_parent_id)
+ addattr32(&req.n, sizeof(req), NET_SHAPER_A_HANDLE_ID, parent_id);
+ addattr_nest_end(&req.n, parent);
+
+ struct rtattr *handle = addattr_nest(&req.n, sizeof(req),
+ NET_SHAPER_A_HANDLE | NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), NET_SHAPER_A_HANDLE_SCOPE, handle_scope);
+ if (has_handle_id)
+ addattr32(&req.n, sizeof(req), NET_SHAPER_A_HANDLE_ID, handle_id);
+ addattr_nest_end(&req.n, handle);
+
+ if (args.has_bw_min)
+ addattr64(&req.n, sizeof(req), NET_SHAPER_A_BW_MIN,
+ args.bw_min_bps);
+ if (args.has_bw_max)
+ addattr64(&req.n, sizeof(req), NET_SHAPER_A_BW_MAX,
+ args.bw_max_bps);
+ if (args.has_weight)
+ addattr32(&req.n, sizeof(req), NET_SHAPER_A_WEIGHT, args.weight);
+
+ if (args.has_bw_min || args.has_bw_max)
+ addattr32(&req.n, sizeof(req), NET_SHAPER_A_METRIC,
+ NET_SHAPER_METRIC_BPS);
+
+ for (int i = 0; i < num_leaves; i++) {
+ struct rtattr *leaf, *leaf_handle;
+
+ leaf = addattr_nest(&req.n, sizeof(req),
+ NET_SHAPER_A_LEAVES | NLA_F_NESTED);
+ leaf_handle = addattr_nest(&req.n, sizeof(req),
+ NET_SHAPER_A_HANDLE | NLA_F_NESTED);
+ addattr32(&req.n, sizeof(req), NET_SHAPER_A_HANDLE_SCOPE,
+ leaves[i].scope);
+ addattr32(&req.n, sizeof(req), NET_SHAPER_A_HANDLE_ID,
+ leaves[i].id);
+ addattr_nest_end(&req.n, leaf_handle);
+ addattr_nest_end(&req.n, leaf);
+ }
+
+ err = rtnl_talk(&gen_rth, &req.n, &answer);
+ if (err < 0) {
+ fprintf(stderr, "Kernel command failed: %d\n", err);
+ return err;
+ }
+
+ print_netshaper_attrs(answer);
+ free(answer);
+ return 0;
+}
+
int main(int argc, char **argv)
{
int color = default_color_opt();
@@ -363,6 +579,8 @@ int main(int argc, char **argv)
return do_cmd(argc - 1, argv + 1, NET_SHAPER_CMD_DELETE);
if (strcmp(*argv, "show") == 0)
return do_cmd(argc - 1, argv + 1, NET_SHAPER_CMD_GET);
+ if (strcmp(*argv, "group") == 0)
+ return do_group(argc - 1, argv + 1);
if (strcmp(*argv, "help") == 0) {
usage();
return 0;
--
2.53.0-Meta
next prev parent reply other threads:[~2026-05-11 18:39 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-11 18:39 [PATCH iproute2-next v3 0/6] netshaper: Extend netshaper support Mohsin Bashir
2026-05-11 18:39 ` [PATCH iproute2-next v3 1/6] netshaper: Extract parse_scope() and parse_rate() helpers Mohsin Bashir
2026-05-11 18:39 ` [PATCH iproute2-next v3 2/6] netshaper: Add bw-min and weight parameter support Mohsin Bashir
2026-05-11 18:39 ` [PATCH iproute2-next v3 3/6] netshaper: Extend show output with parent, bw-min and weight Mohsin Bashir
2026-05-11 18:39 ` [PATCH iproute2-next v3 4/6] netshaper: Extract struct shaper_args and parse_shaper_arg() helper Mohsin Bashir
2026-05-11 18:39 ` Mohsin Bashir [this message]
2026-05-11 18:39 ` [PATCH iproute2-next v3 6/6] netshaper: Update man page for new parameters and group command Mohsin Bashir
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260511183915.797792-6-mohsin.bashr@gmail.com \
--to=mohsin.bashr@gmail.com \
--cc=alexanderduyck@gmail.com \
--cc=dsahern@kernel.org \
--cc=ernis@linux.microsoft.com \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=noreply@anthropic.com \
--cc=pabeni@redhat.com \
--cc=stephen@networkplumber.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox