mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2025-01-02 11:54:36 +08:00
13defa275e
- Introduce matchall filter support - Add SPAN API to configure port mirroring. - Add tc mirror action. At this moment, only mirror (egress) action is supported. Example: tc filter ... action mirred egress mirror dev DEV Co-developed-by: Volodymyr Mytnyk <vmytnyk@marvell.com> Signed-off-by: Volodymyr Mytnyk <vmytnyk@marvell.com> Signed-off-by: Serhiy Boiko <serhiy.boiko@plvision.eu> Signed-off-by: Vadym Kochan <vkochan@marvell.com> Signed-off-by: David S. Miller <davem@davemloft.net>
377 lines
8.0 KiB
C
377 lines
8.0 KiB
C
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
|
|
/* Copyright (c) 2020 Marvell International Ltd. All rights reserved */
|
|
|
|
#include <linux/rhashtable.h>
|
|
|
|
#include "prestera.h"
|
|
#include "prestera_hw.h"
|
|
#include "prestera_acl.h"
|
|
#include "prestera_span.h"
|
|
|
|
struct prestera_acl {
|
|
struct prestera_switch *sw;
|
|
struct list_head rules;
|
|
};
|
|
|
|
struct prestera_acl_ruleset {
|
|
struct rhashtable rule_ht;
|
|
struct prestera_switch *sw;
|
|
u16 id;
|
|
};
|
|
|
|
struct prestera_acl_rule {
|
|
struct rhash_head ht_node;
|
|
struct list_head list;
|
|
struct list_head match_list;
|
|
struct list_head action_list;
|
|
struct prestera_flow_block *block;
|
|
unsigned long cookie;
|
|
u32 priority;
|
|
u8 n_actions;
|
|
u8 n_matches;
|
|
u32 id;
|
|
};
|
|
|
|
static const struct rhashtable_params prestera_acl_rule_ht_params = {
|
|
.key_len = sizeof(unsigned long),
|
|
.key_offset = offsetof(struct prestera_acl_rule, cookie),
|
|
.head_offset = offsetof(struct prestera_acl_rule, ht_node),
|
|
.automatic_shrinking = true,
|
|
};
|
|
|
|
static struct prestera_acl_ruleset *
|
|
prestera_acl_ruleset_create(struct prestera_switch *sw)
|
|
{
|
|
struct prestera_acl_ruleset *ruleset;
|
|
int err;
|
|
|
|
ruleset = kzalloc(sizeof(*ruleset), GFP_KERNEL);
|
|
if (!ruleset)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
err = rhashtable_init(&ruleset->rule_ht, &prestera_acl_rule_ht_params);
|
|
if (err)
|
|
goto err_rhashtable_init;
|
|
|
|
err = prestera_hw_acl_ruleset_create(sw, &ruleset->id);
|
|
if (err)
|
|
goto err_ruleset_create;
|
|
|
|
ruleset->sw = sw;
|
|
|
|
return ruleset;
|
|
|
|
err_ruleset_create:
|
|
rhashtable_destroy(&ruleset->rule_ht);
|
|
err_rhashtable_init:
|
|
kfree(ruleset);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
static void prestera_acl_ruleset_destroy(struct prestera_acl_ruleset *ruleset)
|
|
{
|
|
prestera_hw_acl_ruleset_del(ruleset->sw, ruleset->id);
|
|
rhashtable_destroy(&ruleset->rule_ht);
|
|
kfree(ruleset);
|
|
}
|
|
|
|
struct prestera_flow_block *
|
|
prestera_acl_block_create(struct prestera_switch *sw, struct net *net)
|
|
{
|
|
struct prestera_flow_block *block;
|
|
|
|
block = kzalloc(sizeof(*block), GFP_KERNEL);
|
|
if (!block)
|
|
return NULL;
|
|
INIT_LIST_HEAD(&block->binding_list);
|
|
block->net = net;
|
|
block->sw = sw;
|
|
|
|
block->ruleset = prestera_acl_ruleset_create(sw);
|
|
if (IS_ERR(block->ruleset)) {
|
|
kfree(block);
|
|
return NULL;
|
|
}
|
|
|
|
return block;
|
|
}
|
|
|
|
void prestera_acl_block_destroy(struct prestera_flow_block *block)
|
|
{
|
|
prestera_acl_ruleset_destroy(block->ruleset);
|
|
WARN_ON(!list_empty(&block->binding_list));
|
|
kfree(block);
|
|
}
|
|
|
|
static struct prestera_flow_block_binding *
|
|
prestera_acl_block_lookup(struct prestera_flow_block *block,
|
|
struct prestera_port *port)
|
|
{
|
|
struct prestera_flow_block_binding *binding;
|
|
|
|
list_for_each_entry(binding, &block->binding_list, list)
|
|
if (binding->port == port)
|
|
return binding;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int prestera_acl_block_bind(struct prestera_flow_block *block,
|
|
struct prestera_port *port)
|
|
{
|
|
struct prestera_flow_block_binding *binding;
|
|
int err;
|
|
|
|
if (WARN_ON(prestera_acl_block_lookup(block, port)))
|
|
return -EEXIST;
|
|
|
|
binding = kzalloc(sizeof(*binding), GFP_KERNEL);
|
|
if (!binding)
|
|
return -ENOMEM;
|
|
binding->span_id = PRESTERA_SPAN_INVALID_ID;
|
|
binding->port = port;
|
|
|
|
err = prestera_hw_acl_port_bind(port, block->ruleset->id);
|
|
if (err)
|
|
goto err_rules_bind;
|
|
|
|
list_add(&binding->list, &block->binding_list);
|
|
return 0;
|
|
|
|
err_rules_bind:
|
|
kfree(binding);
|
|
return err;
|
|
}
|
|
|
|
int prestera_acl_block_unbind(struct prestera_flow_block *block,
|
|
struct prestera_port *port)
|
|
{
|
|
struct prestera_flow_block_binding *binding;
|
|
|
|
binding = prestera_acl_block_lookup(block, port);
|
|
if (!binding)
|
|
return -ENOENT;
|
|
|
|
list_del(&binding->list);
|
|
|
|
prestera_hw_acl_port_unbind(port, block->ruleset->id);
|
|
|
|
kfree(binding);
|
|
return 0;
|
|
}
|
|
|
|
struct prestera_acl_ruleset *
|
|
prestera_acl_block_ruleset_get(struct prestera_flow_block *block)
|
|
{
|
|
return block->ruleset;
|
|
}
|
|
|
|
u16 prestera_acl_rule_ruleset_id_get(const struct prestera_acl_rule *rule)
|
|
{
|
|
return rule->block->ruleset->id;
|
|
}
|
|
|
|
struct net *prestera_acl_block_net(struct prestera_flow_block *block)
|
|
{
|
|
return block->net;
|
|
}
|
|
|
|
struct prestera_switch *prestera_acl_block_sw(struct prestera_flow_block *block)
|
|
{
|
|
return block->sw;
|
|
}
|
|
|
|
struct prestera_acl_rule *
|
|
prestera_acl_rule_lookup(struct prestera_acl_ruleset *ruleset,
|
|
unsigned long cookie)
|
|
{
|
|
return rhashtable_lookup_fast(&ruleset->rule_ht, &cookie,
|
|
prestera_acl_rule_ht_params);
|
|
}
|
|
|
|
struct prestera_acl_rule *
|
|
prestera_acl_rule_create(struct prestera_flow_block *block,
|
|
unsigned long cookie)
|
|
{
|
|
struct prestera_acl_rule *rule;
|
|
|
|
rule = kzalloc(sizeof(*rule), GFP_KERNEL);
|
|
if (!rule)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
INIT_LIST_HEAD(&rule->match_list);
|
|
INIT_LIST_HEAD(&rule->action_list);
|
|
rule->cookie = cookie;
|
|
rule->block = block;
|
|
|
|
return rule;
|
|
}
|
|
|
|
struct list_head *
|
|
prestera_acl_rule_match_list_get(struct prestera_acl_rule *rule)
|
|
{
|
|
return &rule->match_list;
|
|
}
|
|
|
|
struct list_head *
|
|
prestera_acl_rule_action_list_get(struct prestera_acl_rule *rule)
|
|
{
|
|
return &rule->action_list;
|
|
}
|
|
|
|
int prestera_acl_rule_action_add(struct prestera_acl_rule *rule,
|
|
struct prestera_acl_rule_action_entry *entry)
|
|
{
|
|
struct prestera_acl_rule_action_entry *a_entry;
|
|
|
|
a_entry = kmalloc(sizeof(*a_entry), GFP_KERNEL);
|
|
if (!a_entry)
|
|
return -ENOMEM;
|
|
|
|
memcpy(a_entry, entry, sizeof(*entry));
|
|
list_add(&a_entry->list, &rule->action_list);
|
|
|
|
rule->n_actions++;
|
|
return 0;
|
|
}
|
|
|
|
u8 prestera_acl_rule_action_len(struct prestera_acl_rule *rule)
|
|
{
|
|
return rule->n_actions;
|
|
}
|
|
|
|
u32 prestera_acl_rule_priority_get(struct prestera_acl_rule *rule)
|
|
{
|
|
return rule->priority;
|
|
}
|
|
|
|
void prestera_acl_rule_priority_set(struct prestera_acl_rule *rule,
|
|
u32 priority)
|
|
{
|
|
rule->priority = priority;
|
|
}
|
|
|
|
int prestera_acl_rule_match_add(struct prestera_acl_rule *rule,
|
|
struct prestera_acl_rule_match_entry *entry)
|
|
{
|
|
struct prestera_acl_rule_match_entry *m_entry;
|
|
|
|
m_entry = kmalloc(sizeof(*m_entry), GFP_KERNEL);
|
|
if (!m_entry)
|
|
return -ENOMEM;
|
|
|
|
memcpy(m_entry, entry, sizeof(*entry));
|
|
list_add(&m_entry->list, &rule->match_list);
|
|
|
|
rule->n_matches++;
|
|
return 0;
|
|
}
|
|
|
|
u8 prestera_acl_rule_match_len(struct prestera_acl_rule *rule)
|
|
{
|
|
return rule->n_matches;
|
|
}
|
|
|
|
void prestera_acl_rule_destroy(struct prestera_acl_rule *rule)
|
|
{
|
|
struct prestera_acl_rule_action_entry *a_entry;
|
|
struct prestera_acl_rule_match_entry *m_entry;
|
|
struct list_head *pos, *n;
|
|
|
|
list_for_each_safe(pos, n, &rule->match_list) {
|
|
m_entry = list_entry(pos, typeof(*m_entry), list);
|
|
list_del(pos);
|
|
kfree(m_entry);
|
|
}
|
|
|
|
list_for_each_safe(pos, n, &rule->action_list) {
|
|
a_entry = list_entry(pos, typeof(*a_entry), list);
|
|
list_del(pos);
|
|
kfree(a_entry);
|
|
}
|
|
|
|
kfree(rule);
|
|
}
|
|
|
|
int prestera_acl_rule_add(struct prestera_switch *sw,
|
|
struct prestera_acl_rule *rule)
|
|
{
|
|
u32 rule_id;
|
|
int err;
|
|
|
|
/* try to add rule to hash table first */
|
|
err = rhashtable_insert_fast(&rule->block->ruleset->rule_ht,
|
|
&rule->ht_node,
|
|
prestera_acl_rule_ht_params);
|
|
if (err)
|
|
return err;
|
|
|
|
/* add rule to hw */
|
|
err = prestera_hw_acl_rule_add(sw, rule, &rule_id);
|
|
if (err)
|
|
goto err_rule_add;
|
|
|
|
rule->id = rule_id;
|
|
|
|
list_add_tail(&rule->list, &sw->acl->rules);
|
|
|
|
return 0;
|
|
|
|
err_rule_add:
|
|
rhashtable_remove_fast(&rule->block->ruleset->rule_ht, &rule->ht_node,
|
|
prestera_acl_rule_ht_params);
|
|
return err;
|
|
}
|
|
|
|
void prestera_acl_rule_del(struct prestera_switch *sw,
|
|
struct prestera_acl_rule *rule)
|
|
{
|
|
rhashtable_remove_fast(&rule->block->ruleset->rule_ht, &rule->ht_node,
|
|
prestera_acl_rule_ht_params);
|
|
list_del(&rule->list);
|
|
prestera_hw_acl_rule_del(sw, rule->id);
|
|
}
|
|
|
|
int prestera_acl_rule_get_stats(struct prestera_switch *sw,
|
|
struct prestera_acl_rule *rule,
|
|
u64 *packets, u64 *bytes, u64 *last_use)
|
|
{
|
|
u64 current_packets;
|
|
u64 current_bytes;
|
|
int err;
|
|
|
|
err = prestera_hw_acl_rule_stats_get(sw, rule->id, ¤t_packets,
|
|
¤t_bytes);
|
|
if (err)
|
|
return err;
|
|
|
|
*packets = current_packets;
|
|
*bytes = current_bytes;
|
|
*last_use = jiffies;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int prestera_acl_init(struct prestera_switch *sw)
|
|
{
|
|
struct prestera_acl *acl;
|
|
|
|
acl = kzalloc(sizeof(*acl), GFP_KERNEL);
|
|
if (!acl)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&acl->rules);
|
|
sw->acl = acl;
|
|
acl->sw = sw;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void prestera_acl_fini(struct prestera_switch *sw)
|
|
{
|
|
struct prestera_acl *acl = sw->acl;
|
|
|
|
WARN_ON(!list_empty(&acl->rules));
|
|
kfree(acl);
|
|
}
|