diff --git a/net/shaper/shaper.c b/net/shaper/shaper.c
index 8ecbfd9002be..87db541de646 100644
--- a/net/shaper/shaper.c
+++ b/net/shaper/shaper.c
@@ -48,6 +48,18 @@ static bool is_detached(u32 handle)
 	return net_shaper_handle_scope(handle) == NET_SHAPER_SCOPE_DETACHED;
 }
 
+/* count the number of [multi] attributes of the given type */
+static int attr_list_len(struct genl_info *info, int type)
+{
+	struct nlattr *attr;
+	int rem, cnt = 0;
+
+	nla_for_each_attr_type(attr, type, genlmsg_data(info->genlhdr),
+			       genlmsg_len(info->genlhdr), rem)
+		cnt++;
+	return cnt;
+}
+
 static int fill_handle(struct sk_buff *msg, u32 handle, u32 type,
 		       const struct genl_info *info)
 {
@@ -255,6 +267,27 @@ static void sc_commit(struct net_device *dev, int nr_shapers,
 	xa_unlock(xa);
 }
 
+/* rollback all the tentative inserts from the shaper cache */
+static void sc_rollback(struct net_device *dev)
+{
+	struct xarray *xa = __sc_container(dev);
+	struct net_shaper_info *cur;
+	unsigned long index;
+
+	if (!xa)
+		return;
+
+	xa_lock(xa);
+	xa_for_each_marked(xa, index, cur, XA_MARK_0) {
+		if (is_detached(index))
+			idr_remove(&dev->net_shaper_data->detached_ids,
+				   net_shaper_handle_id(index));
+		__xa_erase(xa, index);
+		kfree(cur);
+	}
+	xa_unlock(xa);
+}
+
 static int parse_handle(const struct nlattr *attr, const struct genl_info *info,
 			u32 *handle)
 {
@@ -354,6 +387,36 @@ static int parse_shaper(struct net_device *dev, const struct nlattr *attr,
 	return __parse_shaper(dev, tb, info, shaper);
 }
 
+/* alike parse_shaper(), but additionally allow the user specifying
+ * the shaper's parent handle
+ */
+static int parse_output_shaper(struct net_device *dev,
+			       const struct nlattr *attr,
+			       const struct genl_info *info,
+			       struct net_shaper_info *shaper)
+{
+	struct nlattr *tb[NET_SHAPER_A_PARENT + 1];
+	int ret;
+
+	ret = nla_parse_nested(tb, NET_SHAPER_A_PARENT, attr,
+			       net_shaper_ns_output_info_nl_policy,
+			       info->extack);
+	if (ret < 0)
+		return ret;
+
+	ret = __parse_shaper(dev, tb, info, shaper);
+	if (ret)
+		return ret;
+
+	if (tb[NET_SHAPER_A_PARENT]) {
+		ret = parse_handle(tb[NET_SHAPER_A_PARENT], info,
+				   &shaper->parent);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
 int net_shaper_nl_get_doit(struct sk_buff *skb, struct genl_info *info)
 {
 	struct net_shaper_info *shaper;
@@ -569,9 +632,209 @@ int net_shaper_nl_delete_doit(struct sk_buff *skb, struct genl_info *info)
 	return ret;
 }
 
+/* Update the H/W and on success update the local cache, too */
+static int net_shaper_group(struct net_device *dev, int nr_inputs,
+			    struct net_shaper_info *inputs,
+			    struct net_shaper_info *output,
+			    struct netlink_ext_ack *extack)
+{
+	enum net_shaper_scope scope, output_scope, output_pscope;
+	struct net_shaper_info *parent = NULL;
+	int i, ret;
+
+	output_scope = net_shaper_handle_scope(output->handle);
+	if (output_scope != NET_SHAPER_SCOPE_DETACHED &&
+	    output_scope != NET_SHAPER_SCOPE_NETDEV) {
+		NL_SET_ERR_MSG_FMT(extack, "Invalid scope %d for output shaper %x",
+				   output_scope, output->handle);
+		return -EINVAL;
+	}
+
+	output_pscope = net_shaper_handle_scope(output->parent);
+	if (output_scope == NET_SHAPER_SCOPE_DETACHED) {
+		if (net_shaper_handle_id(output->handle) !=
+		    NET_SHAPER_ID_UNSPEC && !sc_lookup(dev, output->handle)) {
+			NL_SET_ERR_MSG_FMT(extack, "Output shaper %x does not exists",
+					   output->handle);
+			return -EINVAL;
+		}
+		if (output_pscope != NET_SHAPER_SCOPE_DETACHED &&
+		    output_pscope != NET_SHAPER_SCOPE_NETDEV) {
+			NL_SET_ERR_MSG_FMT(extack, "Invalid scope %d for output parent shaper %x",
+					   output_pscope, output->parent);
+			return -EINVAL;
+		}
+	}
+
+	if (output_pscope == NET_SHAPER_SCOPE_DETACHED) {
+		parent = sc_lookup(dev, output->parent);
+		if (!parent) {
+			NL_SET_ERR_MSG_FMT(extack, "Output parent shaper %x does not exists",
+					   output->parent);
+			return -EINVAL;
+		}
+	}
+
+	/* for newly created detached scope shaper, the following will update
+	 * the handle, due to id allocation
+	 */
+	ret = sc_prepare_insert(dev, &output->handle, extack);
+	if (ret)
+		goto rollback;
+
+	for (i = 0; i < nr_inputs; ++i) {
+		scope = net_shaper_handle_scope(inputs[i].handle);
+		if (scope != NET_SHAPER_SCOPE_QUEUE &&
+		    scope != NET_SHAPER_SCOPE_DETACHED) {
+			ret = -EINVAL;
+			NL_SET_ERR_MSG_FMT(extack, "Invalid scope %d for input shaper %d handle %x",
+					   scope, i, inputs[i].handle);
+			goto rollback;
+		}
+		if (scope == NET_SHAPER_SCOPE_DETACHED &&
+		    !sc_lookup(dev, inputs[i].handle)) {
+			ret = -EINVAL;
+			NL_SET_ERR_MSG_FMT(extack,
+					   "Can't create detached shaper as input %d handle %x",
+					   i, inputs[i].handle);
+			goto rollback;
+		}
+
+		ret = sc_prepare_insert(dev, &inputs[i].handle, extack);
+		if (ret)
+			goto rollback;
+
+		/* the inputs shapers will be nested to the output */
+		if (inputs[i].parent != output->handle) {
+			inputs[i].parent = output->handle;
+			output->children++;
+		}
+	}
+
+	ret = dev->netdev_ops->net_shaper_ops->group(dev, nr_inputs,
+						     inputs, output,
+						     extack);
+	if (ret < 0)
+		goto rollback;
+
+	if (parent)
+		parent->children++;
+	sc_commit(dev, 1, output);
+	sc_commit(dev, nr_inputs, inputs);
+	return ret;
+
+rollback:
+	sc_rollback(dev);
+	return ret;
+}
+
+static int nla_handle_total_size(void)
+{
+	return nla_total_size(nla_total_size(sizeof(u32)) +
+			      nla_total_size(sizeof(u32)));
+}
+
+static int group_send_reply(struct genl_info *info, u32 handle, int id)
+{
+	struct nlattr *handle_attr;
+	struct sk_buff *msg;
+	int ret = -EMSGSIZE;
+	void *hdr;
+
+/* prepare the msg reply in advance, to avoid device operation rollback */
+	msg = genlmsg_new(nla_handle_total_size(), GFP_KERNEL);
+	if (!msg)
+		return ret;
+
+	hdr = genlmsg_iput(msg, info);
+	if (!hdr)
+		goto free_msg;
+
+	handle_attr = nla_nest_start(msg, NET_SHAPER_A_HANDLE);
+	if (!handle_attr)
+		goto free_msg;
+
+	if (nla_put_u32(msg, NET_SHAPER_A_SCOPE,
+			net_shaper_handle_scope(handle)))
+		goto free_msg;
+
+	if (nla_put_u32(msg, NET_SHAPER_A_ID, id))
+		goto free_msg;
+
+	nla_nest_end(msg, handle_attr);
+	genlmsg_end(msg, hdr);
+
+	ret =  genlmsg_reply(msg, info);
+	if (ret)
+		goto free_msg;
+
+	return ret;
+
+free_msg:
+	nlmsg_free(msg);
+	return ret;
+}
+
 int net_shaper_nl_group_doit(struct sk_buff *skb, struct genl_info *info)
 {
-	return -EOPNOTSUPP;
+	struct net_shaper_info *inputs, output;
+	int i, ret, rem, nr_inputs;
+	struct net_device *dev;
+	struct nlattr *attr;
+
+	if (GENL_REQ_ATTR_CHECK(info, NET_SHAPER_A_INPUTS) ||
+	    GENL_REQ_ATTR_CHECK(info, NET_SHAPER_A_OUTPUT))
+		return -EINVAL;
+
+	ret = fetch_dev(info, &dev);
+	if (ret)
+		return ret;
+
+	nr_inputs = attr_list_len(info, NET_SHAPER_A_INPUTS);
+	inputs = kcalloc(nr_inputs, sizeof(struct net_shaper_info), GFP_KERNEL);
+	if (!inputs) {
+		GENL_SET_ERR_MSG_FMT(info, "Can't allocate memory for %d input shapers",
+				     nr_inputs);
+		ret = -ENOMEM;
+		goto put;
+	}
+
+	ret = parse_output_shaper(dev, info->attrs[NET_SHAPER_A_OUTPUT], info,
+				  &output);
+	if (ret)
+		goto free_shapers;
+
+	i = 0;
+	nla_for_each_attr_type(attr, NET_SHAPER_A_INPUTS,
+			       genlmsg_data(info->genlhdr),
+			       genlmsg_len(info->genlhdr), rem) {
+		if (WARN_ON_ONCE(i >= nr_inputs))
+			goto free_shapers;
+
+		ret = parse_shaper(dev, attr, info, &inputs[i++]);
+		if (ret)
+			goto free_shapers;
+	}
+
+	ret = net_shaper_group(dev, nr_inputs, inputs, &output, info->extack);
+	if (ret < 0)
+		goto free_shapers;
+
+	ret = group_send_reply(info, output.handle, ret);
+	if (ret) {
+		/* Error on reply is not fatal to avoid rollback a successful
+		 * configuration
+		 */
+		GENL_SET_ERR_MSG_FMT(info, "Can't send reply %d", ret);
+		ret = 0;
+	}
+
+free_shapers:
+	kfree(inputs);
+
+put:
+	netdev_put(dev, NULL);
+	return ret;
 }
 
 void dev_shaper_flush(struct net_device *dev)
