mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

If you try to create a 'mirror' ACL rule on a port that already has a
mirror rule, prestera_span_rule_add() will fail with EEXIST error.
This forces rollback procedure which destroys existing mirror rule on
hardware leaving it visible in linux.
Add an explicit check for EEXIST to prevent the deletion of the existing
rule but keep user seeing error message:
$ tc filter add dev sw1p1 ... skip_sw action mirred egress mirror dev sw1p2
$ tc filter add dev sw1p1 ... skip_sw action mirred egress mirror dev sw1p3
RTNETLINK answers: File exists
We have an error talking to the kernel
Fixes: 13defa275e
("net: marvell: prestera: Add matchall support")
Signed-off-by: Serhiy Boiko <serhiy.boiko@plvision.eu>
Signed-off-by: Maksym Glubokiy <maksym.glubokiy@plvision.eu>
Signed-off-by: David S. Miller <davem@davemloft.net>
127 lines
3.2 KiB
C
127 lines
3.2 KiB
C
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
|
|
/* Copyright (c) 2019-2022 Marvell International Ltd. All rights reserved */
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
|
|
#include "prestera.h"
|
|
#include "prestera_hw.h"
|
|
#include "prestera_flow.h"
|
|
#include "prestera_flower.h"
|
|
#include "prestera_matchall.h"
|
|
#include "prestera_span.h"
|
|
|
|
static int prestera_mall_prio_check(struct prestera_flow_block *block,
|
|
struct tc_cls_matchall_offload *f)
|
|
{
|
|
u32 flower_prio_min;
|
|
u32 flower_prio_max;
|
|
int err;
|
|
|
|
err = prestera_flower_prio_get(block, f->common.chain_index,
|
|
&flower_prio_min, &flower_prio_max);
|
|
if (err == -ENOENT)
|
|
/* No flower filters installed on this chain. */
|
|
return 0;
|
|
|
|
if (err) {
|
|
NL_SET_ERR_MSG(f->common.extack, "Failed to get flower priorities");
|
|
return err;
|
|
}
|
|
|
|
if (f->common.prio <= flower_prio_max && !block->ingress) {
|
|
NL_SET_ERR_MSG(f->common.extack, "Failed to add in front of existing flower rules");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
if (f->common.prio >= flower_prio_min && block->ingress) {
|
|
NL_SET_ERR_MSG(f->common.extack, "Failed to add behind of existing flower rules");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int prestera_mall_prio_get(struct prestera_flow_block *block,
|
|
u32 *prio_min, u32 *prio_max)
|
|
{
|
|
if (!block->mall.bound)
|
|
return -ENOENT;
|
|
|
|
*prio_min = block->mall.prio_min;
|
|
*prio_max = block->mall.prio_max;
|
|
return 0;
|
|
}
|
|
|
|
static void prestera_mall_prio_update(struct prestera_flow_block *block,
|
|
struct tc_cls_matchall_offload *f)
|
|
{
|
|
block->mall.prio_min = min(block->mall.prio_min, f->common.prio);
|
|
block->mall.prio_max = max(block->mall.prio_max, f->common.prio);
|
|
}
|
|
|
|
int prestera_mall_replace(struct prestera_flow_block *block,
|
|
struct tc_cls_matchall_offload *f)
|
|
{
|
|
struct prestera_flow_block_binding *binding;
|
|
__be16 protocol = f->common.protocol;
|
|
struct flow_action_entry *act;
|
|
struct prestera_port *port;
|
|
int err;
|
|
|
|
if (!flow_offload_has_one_action(&f->rule->action)) {
|
|
NL_SET_ERR_MSG(f->common.extack,
|
|
"Only singular actions are supported");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
act = &f->rule->action.entries[0];
|
|
|
|
if (!prestera_netdev_check(act->dev)) {
|
|
NL_SET_ERR_MSG(f->common.extack,
|
|
"Only Marvell Prestera port is supported");
|
|
return -EINVAL;
|
|
}
|
|
if (!tc_cls_can_offload_and_chain0(act->dev, &f->common))
|
|
return -EOPNOTSUPP;
|
|
if (act->id != FLOW_ACTION_MIRRED)
|
|
return -EOPNOTSUPP;
|
|
if (protocol != htons(ETH_P_ALL))
|
|
return -EOPNOTSUPP;
|
|
|
|
err = prestera_mall_prio_check(block, f);
|
|
if (err)
|
|
return err;
|
|
|
|
port = netdev_priv(act->dev);
|
|
|
|
list_for_each_entry(binding, &block->binding_list, list) {
|
|
err = prestera_span_rule_add(binding, port, block->ingress);
|
|
if (err == -EEXIST)
|
|
return err;
|
|
if (err)
|
|
goto rollback;
|
|
}
|
|
|
|
prestera_mall_prio_update(block, f);
|
|
|
|
block->mall.bound = true;
|
|
return 0;
|
|
|
|
rollback:
|
|
list_for_each_entry_continue_reverse(binding,
|
|
&block->binding_list, list)
|
|
prestera_span_rule_del(binding, block->ingress);
|
|
return err;
|
|
}
|
|
|
|
void prestera_mall_destroy(struct prestera_flow_block *block)
|
|
{
|
|
struct prestera_flow_block_binding *binding;
|
|
|
|
list_for_each_entry(binding, &block->binding_list, list)
|
|
prestera_span_rule_del(binding, block->ingress);
|
|
|
|
block->mall.prio_min = UINT_MAX;
|
|
block->mall.prio_max = 0;
|
|
block->mall.bound = false;
|
|
}
|