mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-29 17:25:38 +00:00
net-shapers: implement NL set and delete operations
Both NL operations directly map on the homonymous device shaper callbacks, update accordingly the shapers cache and are serialized via a per device lock. Implement the cache modification helpers to additionally deal with NODE scope shaper. That will be needed by the group() operation implemented in the next patch. The delete implementation is partial: does not handle NODE scope shaper yet. Such support will require infrastructure from the next patch and will be implemented later in the series. Reviewed-by: Jiri Pirko <jiri@nvidia.com> Signed-off-by: Paolo Abeni <pabeni@redhat.com> Link: https://patch.msgid.link/1e6a34a4095b35d773d2b9c476164671bbcf8397.1728460186.git.pabeni@redhat.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
4b623f9f0f
commit
93954b40f6
@ -36,6 +36,24 @@ static struct net_shaper_binding *net_shaper_binding_from_ctx(void *ctx)
|
||||
return &((struct net_shaper_nl_ctx *)ctx)->binding;
|
||||
}
|
||||
|
||||
static void net_shaper_lock(struct net_shaper_binding *binding)
|
||||
{
|
||||
switch (binding->type) {
|
||||
case NET_SHAPER_BINDING_TYPE_NETDEV:
|
||||
mutex_lock(&binding->netdev->lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void net_shaper_unlock(struct net_shaper_binding *binding)
|
||||
{
|
||||
switch (binding->type) {
|
||||
case NET_SHAPER_BINDING_TYPE_NETDEV:
|
||||
mutex_unlock(&binding->netdev->lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct net_shaper_hierarchy *
|
||||
net_shaper_hierarchy(struct net_shaper_binding *binding)
|
||||
{
|
||||
@ -47,6 +65,16 @@ net_shaper_hierarchy(struct net_shaper_binding *binding)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct net_shaper_ops *
|
||||
net_shaper_ops(struct net_shaper_binding *binding)
|
||||
{
|
||||
if (binding->type == NET_SHAPER_BINDING_TYPE_NETDEV)
|
||||
return binding->netdev->netdev_ops->net_shaper_ops;
|
||||
|
||||
/* No other type supported yet. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int net_shaper_fill_binding(struct sk_buff *msg,
|
||||
const struct net_shaper_binding *binding,
|
||||
u32 type)
|
||||
@ -170,6 +198,37 @@ static u32 net_shaper_handle_to_index(const struct net_shaper_handle *handle)
|
||||
FIELD_PREP(NET_SHAPER_ID_MASK, handle->id);
|
||||
}
|
||||
|
||||
static void net_shaper_index_to_handle(u32 index,
|
||||
struct net_shaper_handle *handle)
|
||||
{
|
||||
handle->scope = FIELD_GET(NET_SHAPER_SCOPE_MASK, index);
|
||||
handle->id = FIELD_GET(NET_SHAPER_ID_MASK, index);
|
||||
}
|
||||
|
||||
static void net_shaper_default_parent(const struct net_shaper_handle *handle,
|
||||
struct net_shaper_handle *parent)
|
||||
{
|
||||
switch (handle->scope) {
|
||||
case NET_SHAPER_SCOPE_UNSPEC:
|
||||
case NET_SHAPER_SCOPE_NETDEV:
|
||||
case __NET_SHAPER_SCOPE_MAX:
|
||||
parent->scope = NET_SHAPER_SCOPE_UNSPEC;
|
||||
break;
|
||||
|
||||
case NET_SHAPER_SCOPE_QUEUE:
|
||||
case NET_SHAPER_SCOPE_NODE:
|
||||
parent->scope = NET_SHAPER_SCOPE_NETDEV;
|
||||
break;
|
||||
}
|
||||
parent->id = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* MARK_0 is already in use due to XA_FLAGS_ALLOC, can't reuse such flag as
|
||||
* it's cleared by xa_store().
|
||||
*/
|
||||
#define NET_SHAPER_NOT_VALID XA_MARK_1
|
||||
|
||||
static struct net_shaper *
|
||||
net_shaper_lookup(struct net_shaper_binding *binding,
|
||||
const struct net_shaper_handle *handle)
|
||||
@ -177,7 +236,154 @@ net_shaper_lookup(struct net_shaper_binding *binding,
|
||||
struct net_shaper_hierarchy *hierarchy = net_shaper_hierarchy(binding);
|
||||
u32 index = net_shaper_handle_to_index(handle);
|
||||
|
||||
return hierarchy ? xa_load(&hierarchy->shapers, index) : NULL;
|
||||
if (!hierarchy || xa_get_mark(&hierarchy->shapers, index,
|
||||
NET_SHAPER_NOT_VALID))
|
||||
return NULL;
|
||||
|
||||
return xa_load(&hierarchy->shapers, index);
|
||||
}
|
||||
|
||||
/* Allocate on demand the per device shaper's hierarchy container.
|
||||
* Called under the net shaper lock
|
||||
*/
|
||||
static struct net_shaper_hierarchy *
|
||||
net_shaper_hierarchy_setup(struct net_shaper_binding *binding)
|
||||
{
|
||||
struct net_shaper_hierarchy *hierarchy = net_shaper_hierarchy(binding);
|
||||
|
||||
if (hierarchy)
|
||||
return hierarchy;
|
||||
|
||||
hierarchy = kmalloc(sizeof(*hierarchy), GFP_KERNEL);
|
||||
if (!hierarchy)
|
||||
return NULL;
|
||||
|
||||
/* The flag is required for ID allocation */
|
||||
xa_init_flags(&hierarchy->shapers, XA_FLAGS_ALLOC);
|
||||
|
||||
switch (binding->type) {
|
||||
case NET_SHAPER_BINDING_TYPE_NETDEV:
|
||||
/* Pairs with READ_ONCE in net_shaper_hierarchy. */
|
||||
WRITE_ONCE(binding->netdev->net_shaper_hierarchy, hierarchy);
|
||||
break;
|
||||
}
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
/* Prepare the hierarchy container to actually insert the given shaper, doing
|
||||
* in advance the needed allocations.
|
||||
*/
|
||||
static int net_shaper_pre_insert(struct net_shaper_binding *binding,
|
||||
struct net_shaper_handle *handle,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct net_shaper_hierarchy *hierarchy = net_shaper_hierarchy(binding);
|
||||
struct net_shaper *prev, *cur;
|
||||
bool id_allocated = false;
|
||||
int ret, index;
|
||||
|
||||
if (!hierarchy)
|
||||
return -ENOMEM;
|
||||
|
||||
index = net_shaper_handle_to_index(handle);
|
||||
cur = xa_load(&hierarchy->shapers, index);
|
||||
if (cur)
|
||||
return 0;
|
||||
|
||||
/* Allocated a new id, if needed. */
|
||||
if (handle->scope == NET_SHAPER_SCOPE_NODE &&
|
||||
handle->id == NET_SHAPER_ID_UNSPEC) {
|
||||
u32 min, max;
|
||||
|
||||
handle->id = NET_SHAPER_ID_MASK - 1;
|
||||
max = net_shaper_handle_to_index(handle);
|
||||
handle->id = 0;
|
||||
min = net_shaper_handle_to_index(handle);
|
||||
|
||||
ret = xa_alloc(&hierarchy->shapers, &index, NULL,
|
||||
XA_LIMIT(min, max), GFP_KERNEL);
|
||||
if (ret < 0) {
|
||||
NL_SET_ERR_MSG(extack, "Can't allocate new id for NODE shaper");
|
||||
return ret;
|
||||
}
|
||||
|
||||
net_shaper_index_to_handle(index, handle);
|
||||
id_allocated = true;
|
||||
}
|
||||
|
||||
cur = kzalloc(sizeof(*cur), GFP_KERNEL);
|
||||
if (!cur) {
|
||||
ret = -ENOMEM;
|
||||
goto free_id;
|
||||
}
|
||||
|
||||
/* Mark 'tentative' shaper inside the hierarchy container.
|
||||
* xa_set_mark is a no-op if the previous store fails.
|
||||
*/
|
||||
xa_lock(&hierarchy->shapers);
|
||||
prev = __xa_store(&hierarchy->shapers, index, cur, GFP_KERNEL);
|
||||
__xa_set_mark(&hierarchy->shapers, index, NET_SHAPER_NOT_VALID);
|
||||
xa_unlock(&hierarchy->shapers);
|
||||
if (xa_err(prev)) {
|
||||
NL_SET_ERR_MSG(extack, "Can't insert shaper into device store");
|
||||
kfree_rcu(cur, rcu);
|
||||
ret = xa_err(prev);
|
||||
goto free_id;
|
||||
}
|
||||
return 0;
|
||||
|
||||
free_id:
|
||||
if (id_allocated)
|
||||
xa_erase(&hierarchy->shapers, index);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Commit the tentative insert with the actual values.
|
||||
* Must be called only after a successful net_shaper_pre_insert().
|
||||
*/
|
||||
static void net_shaper_commit(struct net_shaper_binding *binding,
|
||||
int nr_shapers, const struct net_shaper *shapers)
|
||||
{
|
||||
struct net_shaper_hierarchy *hierarchy = net_shaper_hierarchy(binding);
|
||||
struct net_shaper *cur;
|
||||
int index;
|
||||
int i;
|
||||
|
||||
xa_lock(&hierarchy->shapers);
|
||||
for (i = 0; i < nr_shapers; ++i) {
|
||||
index = net_shaper_handle_to_index(&shapers[i].handle);
|
||||
|
||||
cur = xa_load(&hierarchy->shapers, index);
|
||||
if (WARN_ON_ONCE(!cur))
|
||||
continue;
|
||||
|
||||
/* Successful update: drop the tentative mark
|
||||
* and update the hierarchy container.
|
||||
*/
|
||||
__xa_clear_mark(&hierarchy->shapers, index,
|
||||
NET_SHAPER_NOT_VALID);
|
||||
*cur = shapers[i];
|
||||
}
|
||||
xa_unlock(&hierarchy->shapers);
|
||||
}
|
||||
|
||||
/* Rollback all the tentative inserts from the hierarchy. */
|
||||
static void net_shaper_rollback(struct net_shaper_binding *binding)
|
||||
{
|
||||
struct net_shaper_hierarchy *hierarchy = net_shaper_hierarchy(binding);
|
||||
struct net_shaper *cur;
|
||||
unsigned long index;
|
||||
|
||||
if (!hierarchy)
|
||||
return;
|
||||
|
||||
xa_lock(&hierarchy->shapers);
|
||||
xa_for_each_marked(&hierarchy->shapers, index, cur,
|
||||
NET_SHAPER_NOT_VALID) {
|
||||
__xa_erase(&hierarchy->shapers, index);
|
||||
kfree(cur);
|
||||
}
|
||||
xa_unlock(&hierarchy->shapers);
|
||||
}
|
||||
|
||||
static int net_shaper_parse_handle(const struct nlattr *attr,
|
||||
@ -215,6 +421,57 @@ static int net_shaper_parse_handle(const struct nlattr *attr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int net_shaper_parse_info(struct net_shaper_binding *binding,
|
||||
struct nlattr **tb,
|
||||
const struct genl_info *info,
|
||||
struct net_shaper *shaper,
|
||||
bool *exists)
|
||||
{
|
||||
struct net_shaper *old;
|
||||
int ret;
|
||||
|
||||
/* The shaper handle is the only mandatory attribute. */
|
||||
if (NL_REQ_ATTR_CHECK(info->extack, NULL, tb, NET_SHAPER_A_HANDLE))
|
||||
return -EINVAL;
|
||||
|
||||
ret = net_shaper_parse_handle(tb[NET_SHAPER_A_HANDLE], info,
|
||||
&shaper->handle);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (shaper->handle.scope == NET_SHAPER_SCOPE_UNSPEC) {
|
||||
NL_SET_BAD_ATTR(info->extack, tb[NET_SHAPER_A_HANDLE]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Fetch existing hierarchy, if any, so that user provide info will
|
||||
* incrementally update the existing shaper configuration.
|
||||
*/
|
||||
old = net_shaper_lookup(binding, &shaper->handle);
|
||||
if (old)
|
||||
*shaper = *old;
|
||||
*exists = !!old;
|
||||
|
||||
if (tb[NET_SHAPER_A_METRIC])
|
||||
shaper->metric = nla_get_u32(tb[NET_SHAPER_A_METRIC]);
|
||||
|
||||
if (tb[NET_SHAPER_A_BW_MIN])
|
||||
shaper->bw_min = nla_get_uint(tb[NET_SHAPER_A_BW_MIN]);
|
||||
|
||||
if (tb[NET_SHAPER_A_BW_MAX])
|
||||
shaper->bw_max = nla_get_uint(tb[NET_SHAPER_A_BW_MAX]);
|
||||
|
||||
if (tb[NET_SHAPER_A_BURST])
|
||||
shaper->burst = nla_get_uint(tb[NET_SHAPER_A_BURST]);
|
||||
|
||||
if (tb[NET_SHAPER_A_PRIORITY])
|
||||
shaper->priority = nla_get_u32(tb[NET_SHAPER_A_PRIORITY]);
|
||||
|
||||
if (tb[NET_SHAPER_A_WEIGHT])
|
||||
shaper->weight = nla_get_u32(tb[NET_SHAPER_A_WEIGHT]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int net_shaper_generic_pre(struct genl_info *info, int type)
|
||||
{
|
||||
struct net_shaper_nl_ctx *ctx = (struct net_shaper_nl_ctx *)info->ctx;
|
||||
@ -332,12 +589,129 @@ int net_shaper_nl_get_dumpit(struct sk_buff *skb,
|
||||
|
||||
int net_shaper_nl_set_doit(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
struct net_shaper_hierarchy *hierarchy;
|
||||
struct net_shaper_binding *binding;
|
||||
const struct net_shaper_ops *ops;
|
||||
struct net_shaper_handle handle;
|
||||
struct net_shaper shaper = {};
|
||||
bool exists;
|
||||
int ret;
|
||||
|
||||
binding = net_shaper_binding_from_ctx(info->ctx);
|
||||
|
||||
net_shaper_lock(binding);
|
||||
ret = net_shaper_parse_info(binding, info->attrs, info, &shaper,
|
||||
&exists);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
if (!exists)
|
||||
net_shaper_default_parent(&shaper.handle, &shaper.parent);
|
||||
|
||||
hierarchy = net_shaper_hierarchy_setup(binding);
|
||||
if (!hierarchy) {
|
||||
ret = -ENOMEM;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* The 'set' operation can't create node-scope shapers. */
|
||||
handle = shaper.handle;
|
||||
if (handle.scope == NET_SHAPER_SCOPE_NODE &&
|
||||
!net_shaper_lookup(binding, &handle)) {
|
||||
ret = -ENOENT;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
ret = net_shaper_pre_insert(binding, &handle, info->extack);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
ops = net_shaper_ops(binding);
|
||||
ret = ops->set(binding, &shaper, info->extack);
|
||||
if (ret) {
|
||||
net_shaper_rollback(binding);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
net_shaper_commit(binding, 1, &shaper);
|
||||
|
||||
unlock:
|
||||
net_shaper_unlock(binding);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __net_shaper_delete(struct net_shaper_binding *binding,
|
||||
struct net_shaper *shaper,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct net_shaper_hierarchy *hierarchy = net_shaper_hierarchy(binding);
|
||||
struct net_shaper_handle parent_handle, handle = shaper->handle;
|
||||
const struct net_shaper_ops *ops = net_shaper_ops(binding);
|
||||
int ret;
|
||||
|
||||
again:
|
||||
parent_handle = shaper->parent;
|
||||
|
||||
ret = ops->delete(binding, &handle, extack);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
xa_erase(&hierarchy->shapers, net_shaper_handle_to_index(&handle));
|
||||
kfree_rcu(shaper, rcu);
|
||||
|
||||
/* Eventually delete the parent, if it is left over with no leaves. */
|
||||
if (parent_handle.scope == NET_SHAPER_SCOPE_NODE) {
|
||||
shaper = net_shaper_lookup(binding, &parent_handle);
|
||||
if (shaper && !--shaper->leaves) {
|
||||
handle = parent_handle;
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int net_shaper_nl_delete_doit(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
struct net_shaper_hierarchy *hierarchy;
|
||||
struct net_shaper_binding *binding;
|
||||
struct net_shaper_handle handle;
|
||||
struct net_shaper *shaper;
|
||||
int ret;
|
||||
|
||||
if (GENL_REQ_ATTR_CHECK(info, NET_SHAPER_A_HANDLE))
|
||||
return -EINVAL;
|
||||
|
||||
binding = net_shaper_binding_from_ctx(info->ctx);
|
||||
|
||||
net_shaper_lock(binding);
|
||||
ret = net_shaper_parse_handle(info->attrs[NET_SHAPER_A_HANDLE], info,
|
||||
&handle);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
hierarchy = net_shaper_hierarchy(binding);
|
||||
if (!hierarchy) {
|
||||
ret = -ENOENT;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
shaper = net_shaper_lookup(binding, &handle);
|
||||
if (!shaper) {
|
||||
ret = -ENOENT;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (handle.scope == NET_SHAPER_SCOPE_NODE) {
|
||||
/* TODO: implement support for scope NODE delete. */
|
||||
ret = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
ret = __net_shaper_delete(binding, shaper, info->extack);
|
||||
|
||||
unlock:
|
||||
net_shaper_unlock(binding);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void net_shaper_flush(struct net_shaper_binding *binding)
|
||||
@ -349,12 +723,15 @@ static void net_shaper_flush(struct net_shaper_binding *binding)
|
||||
if (!hierarchy)
|
||||
return;
|
||||
|
||||
net_shaper_lock(binding);
|
||||
xa_lock(&hierarchy->shapers);
|
||||
xa_for_each(&hierarchy->shapers, index, cur) {
|
||||
__xa_erase(&hierarchy->shapers, index);
|
||||
kfree(cur);
|
||||
}
|
||||
xa_unlock(&hierarchy->shapers);
|
||||
net_shaper_unlock(binding);
|
||||
|
||||
kfree(hierarchy);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user