Merge branch 'nfp-flower-police-validation-and-ct-enhancements'

Simon Horman says:

====================
nfp: flower: police validation and ct enhancements

this series enhances the flower hardware offload
facility provided by the nfp driver.

1. Add validation of police actions created independently of flows

2. Add support offload of ct NAT action

3. Support offload of rule which has both vlan push/pop/mangle
   and ct action
====================

Link: https://lore.kernel.org/r/20220914160604.1740282-1-simon.horman@corigine.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2022-09-20 13:25:15 -07:00
commit 4d3c884850
3 changed files with 263 additions and 16 deletions

View file

@ -1,6 +1,9 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright (C) 2021 Corigine, Inc. */
#include <net/tc_act/tc_csum.h>
#include <net/tc_act/tc_ct.h>
#include "conntrack.h"
#include "../nfp_port.h"
@ -56,9 +59,17 @@ bool is_pre_ct_flow(struct flow_cls_offload *flow)
int i;
flow_action_for_each(i, act, &flow->rule->action) {
if (act->id == FLOW_ACTION_CT && !act->ct.action)
return true;
if (act->id == FLOW_ACTION_CT) {
/* The pre_ct rule only have the ct or ct nat action, cannot
* contains other ct action e.g ct commit and so on.
*/
if ((!act->ct.action || act->ct.action == TCA_CT_ACT_NAT))
return true;
else
return false;
}
}
return false;
}
@ -66,13 +77,37 @@ bool is_post_ct_flow(struct flow_cls_offload *flow)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(flow);
struct flow_dissector *dissector = rule->match.dissector;
struct flow_action_entry *act;
bool exist_ct_clear = false;
struct flow_match_ct ct;
int i;
/* post ct entry cannot contains any ct action except ct_clear. */
flow_action_for_each(i, act, &flow->rule->action) {
if (act->id == FLOW_ACTION_CT) {
/* ignore ct clear action. */
if (act->ct.action == TCA_CT_ACT_CLEAR) {
exist_ct_clear = true;
continue;
}
return false;
}
}
if (dissector->used_keys & BIT(FLOW_DISSECTOR_KEY_CT)) {
flow_rule_match_ct(rule, &ct);
if (ct.key->ct_state & TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED)
return true;
} else {
/* when do nat with ct, the post ct entry ignore the ct status,
* will match the nat field(sip/dip) instead. In this situation,
* the flow chain index is not zero and contains ct clear action.
*/
if (flow->common.chain_index && exist_ct_clear)
return true;
}
return false;
}
@ -168,6 +203,20 @@ static void *get_mangled_tos_ttl(struct flow_rule *rule, void *buf,
return buf;
}
/* Note entry1 and entry2 are not swappable. only skip ip and
* tport merge check for pre_ct and post_ct when pre_ct do nat.
*/
static bool nfp_ct_merge_check_cannot_skip(struct nfp_fl_ct_flow_entry *entry1,
struct nfp_fl_ct_flow_entry *entry2)
{
/* only pre_ct have NFP_FL_ACTION_DO_NAT flag. */
if ((entry1->flags & NFP_FL_ACTION_DO_NAT) &&
entry2->type == CT_TYPE_POST_CT)
return false;
return true;
}
/* Note entry1 and entry2 are not swappable, entry1 should be
* the former flow whose mangle action need be taken into account
* if existed, and entry2 should be the latter flow whose action
@ -225,7 +274,12 @@ static int nfp_ct_merge_check(struct nfp_fl_ct_flow_entry *entry1,
goto check_failed;
}
if (ovlp_keys & BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {
/* if pre ct entry do nat, the nat ip exists in nft entry,
* will be do merge check when do nft and post ct merge,
* so skip this ip merge check here.
*/
if ((ovlp_keys & BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS)) &&
nfp_ct_merge_check_cannot_skip(entry1, entry2)) {
struct flow_match_ipv4_addrs match1, match2;
flow_rule_match_ipv4_addrs(entry1->rule, &match1);
@ -242,7 +296,12 @@ static int nfp_ct_merge_check(struct nfp_fl_ct_flow_entry *entry1,
goto check_failed;
}
if (ovlp_keys & BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS)) {
/* if pre ct entry do nat, the nat ip exists in nft entry,
* will be do merge check when do nft and post ct merge,
* so skip this ip merge check here.
*/
if ((ovlp_keys & BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS)) &&
nfp_ct_merge_check_cannot_skip(entry1, entry2)) {
struct flow_match_ipv6_addrs match1, match2;
flow_rule_match_ipv6_addrs(entry1->rule, &match1);
@ -259,7 +318,12 @@ static int nfp_ct_merge_check(struct nfp_fl_ct_flow_entry *entry1,
goto check_failed;
}
if (ovlp_keys & BIT(FLOW_DISSECTOR_KEY_PORTS)) {
/* if pre ct entry do nat, the nat tport exists in nft entry,
* will be do merge check when do nft and post ct merge,
* so skip this tport merge check here.
*/
if ((ovlp_keys & BIT(FLOW_DISSECTOR_KEY_PORTS)) &&
nfp_ct_merge_check_cannot_skip(entry1, entry2)) {
enum flow_action_mangle_base htype = FLOW_ACT_MANGLE_UNSPEC;
struct flow_match_ports match1, match2;
@ -404,12 +468,55 @@ static int nfp_ct_merge_check(struct nfp_fl_ct_flow_entry *entry1,
return -EINVAL;
}
static int nfp_ct_check_vlan_merge(struct flow_action_entry *a_in,
struct flow_rule *rule)
{
struct flow_match_vlan match;
if (unlikely(flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CVLAN)))
return -EOPNOTSUPP;
/* post_ct does not match VLAN KEY, can be merged. */
if (likely(!flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)))
return 0;
switch (a_in->id) {
/* pre_ct has pop vlan, post_ct cannot match VLAN KEY, cannot be merged. */
case FLOW_ACTION_VLAN_POP:
return -EOPNOTSUPP;
case FLOW_ACTION_VLAN_PUSH:
case FLOW_ACTION_VLAN_MANGLE:
flow_rule_match_vlan(rule, &match);
/* different vlan id, cannot be merged. */
if ((match.key->vlan_id & match.mask->vlan_id) ^
(a_in->vlan.vid & match.mask->vlan_id))
return -EOPNOTSUPP;
/* different tpid, cannot be merged. */
if ((match.key->vlan_tpid & match.mask->vlan_tpid) ^
(a_in->vlan.proto & match.mask->vlan_tpid))
return -EOPNOTSUPP;
/* different priority, cannot be merged. */
if ((match.key->vlan_priority & match.mask->vlan_priority) ^
(a_in->vlan.prio & match.mask->vlan_priority))
return -EOPNOTSUPP;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int nfp_ct_merge_act_check(struct nfp_fl_ct_flow_entry *pre_ct_entry,
struct nfp_fl_ct_flow_entry *post_ct_entry,
struct nfp_fl_ct_flow_entry *nft_entry)
{
struct flow_action_entry *act;
int i;
int i, err;
/* Check for pre_ct->action conflicts */
flow_action_for_each(i, act, &pre_ct_entry->rule->action) {
@ -417,6 +524,10 @@ static int nfp_ct_merge_act_check(struct nfp_fl_ct_flow_entry *pre_ct_entry,
case FLOW_ACTION_VLAN_PUSH:
case FLOW_ACTION_VLAN_POP:
case FLOW_ACTION_VLAN_MANGLE:
err = nfp_ct_check_vlan_merge(act, post_ct_entry->rule);
if (err)
return err;
break;
case FLOW_ACTION_MPLS_PUSH:
case FLOW_ACTION_MPLS_POP:
case FLOW_ACTION_MPLS_MANGLE:
@ -468,6 +579,12 @@ static int nfp_ct_check_meta(struct nfp_fl_ct_flow_entry *post_ct_entry,
return -EINVAL;
return 0;
} else {
/* post_ct with ct clear action will not match the
* ct status when nft is nat entry.
*/
if (nft_entry->flags & NFP_FL_ACTION_DO_MANGLE)
return 0;
}
return -EINVAL;
@ -537,11 +654,37 @@ nfp_fl_calc_key_layers_sz(struct nfp_fl_key_ls in_key_ls, uint16_t *map)
return key_size;
}
/* get the csum flag according the ip proto and mangle action. */
static void nfp_fl_get_csum_flag(struct flow_action_entry *a_in, u8 ip_proto, u32 *csum)
{
if (a_in->id != FLOW_ACTION_MANGLE)
return;
switch (a_in->mangle.htype) {
case FLOW_ACT_MANGLE_HDR_TYPE_IP4:
*csum |= TCA_CSUM_UPDATE_FLAG_IPV4HDR;
if (ip_proto == IPPROTO_TCP)
*csum |= TCA_CSUM_UPDATE_FLAG_TCP;
else if (ip_proto == IPPROTO_UDP)
*csum |= TCA_CSUM_UPDATE_FLAG_UDP;
break;
case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
*csum |= TCA_CSUM_UPDATE_FLAG_TCP;
break;
case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
*csum |= TCA_CSUM_UPDATE_FLAG_UDP;
break;
default:
break;
}
}
static int nfp_fl_merge_actions_offload(struct flow_rule **rules,
struct nfp_flower_priv *priv,
struct net_device *netdev,
struct nfp_fl_payload *flow_pay)
{
enum flow_action_hw_stats tmp_stats = FLOW_ACTION_HW_STATS_DONT_CARE;
struct flow_action_entry *a_in;
int i, j, num_actions, id;
struct flow_rule *a_rule;
@ -551,15 +694,25 @@ static int nfp_fl_merge_actions_offload(struct flow_rule **rules,
rules[CT_TYPE_NFT]->action.num_entries +
rules[CT_TYPE_POST_CT]->action.num_entries;
a_rule = flow_rule_alloc(num_actions);
/* Add one action to make sure there is enough room to add an checksum action
* when do nat.
*/
a_rule = flow_rule_alloc(num_actions + 1);
if (!a_rule)
return -ENOMEM;
/* Actions need a BASIC dissector. */
a_rule->match = rules[CT_TYPE_PRE_CT]->match;
/* post_ct entry have one action at least. */
if (rules[CT_TYPE_POST_CT]->action.num_entries != 0) {
tmp_stats = rules[CT_TYPE_POST_CT]->action.entries[0].hw_stats;
}
/* Copy actions */
for (j = 0; j < _CT_TYPE_MAX; j++) {
u32 csum_updated = 0;
u8 ip_proto = 0;
if (flow_rule_match_key(rules[j], FLOW_DISSECTOR_KEY_BASIC)) {
struct flow_match_basic match;
@ -571,8 +724,10 @@ static int nfp_fl_merge_actions_offload(struct flow_rule **rules,
* through the subflows and assign the proper subflow to a_rule
*/
flow_rule_match_basic(rules[j], &match);
if (match.mask->ip_proto)
if (match.mask->ip_proto) {
a_rule->match = rules[j]->match;
ip_proto = match.key->ip_proto;
}
}
for (i = 0; i < rules[j]->action.num_entries; i++) {
@ -589,11 +744,32 @@ static int nfp_fl_merge_actions_offload(struct flow_rule **rules,
case FLOW_ACTION_CT_METADATA:
continue;
default:
/* nft entry is generated by tc ct, which mangle action do not care
* the stats, inherit the post entry stats to meet the
* flow_action_hw_stats_check.
*/
if (j == CT_TYPE_NFT) {
if (a_in->hw_stats == FLOW_ACTION_HW_STATS_DONT_CARE)
a_in->hw_stats = tmp_stats;
nfp_fl_get_csum_flag(a_in, ip_proto, &csum_updated);
}
memcpy(&a_rule->action.entries[offset++],
a_in, sizeof(struct flow_action_entry));
break;
}
}
/* nft entry have mangle action, but do not have checksum action when do NAT,
* hardware will automatically fix IPv4 and TCP/UDP checksum. so add an csum action
* to meet csum action check.
*/
if (csum_updated) {
struct flow_action_entry *csum_action;
csum_action = &a_rule->action.entries[offset++];
csum_action->id = FLOW_ACTION_CSUM;
csum_action->csum_flags = csum_updated;
csum_action->hw_stats = tmp_stats;
}
}
/* Some actions would have been ignored, so update the num_entries field */
@ -1191,6 +1367,49 @@ static struct net_device *get_netdev_from_rule(struct flow_rule *rule)
return NULL;
}
static void nfp_nft_ct_translate_mangle_action(struct flow_action_entry *mangle_action)
{
if (mangle_action->id != FLOW_ACTION_MANGLE)
return;
switch (mangle_action->mangle.htype) {
case FLOW_ACT_MANGLE_HDR_TYPE_IP4:
case FLOW_ACT_MANGLE_HDR_TYPE_IP6:
mangle_action->mangle.val = (__force u32)cpu_to_be32(mangle_action->mangle.val);
mangle_action->mangle.mask = (__force u32)cpu_to_be32(mangle_action->mangle.mask);
return;
case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
mangle_action->mangle.val = (__force u16)cpu_to_be16(mangle_action->mangle.val);
mangle_action->mangle.mask = (__force u16)cpu_to_be16(mangle_action->mangle.mask);
return;
default:
return;
}
}
static int nfp_nft_ct_set_flow_flag(struct flow_action_entry *act,
struct nfp_fl_ct_flow_entry *entry)
{
switch (act->id) {
case FLOW_ACTION_CT:
if (act->ct.action == TCA_CT_ACT_NAT)
entry->flags |= NFP_FL_ACTION_DO_NAT;
break;
case FLOW_ACTION_MANGLE:
entry->flags |= NFP_FL_ACTION_DO_MANGLE;
break;
default:
break;
}
return 0;
}
static struct
nfp_fl_ct_flow_entry *nfp_fl_ct_add_flow(struct nfp_fl_ct_zone_entry *zt,
struct net_device *netdev,
@ -1257,6 +1476,13 @@ nfp_fl_ct_flow_entry *nfp_fl_ct_add_flow(struct nfp_fl_ct_zone_entry *zt,
new_act = &entry->rule->action.entries[i];
memcpy(new_act, act, sizeof(struct flow_action_entry));
/* nft entry mangle field is host byte order, need translate to
* network byte order.
*/
if (is_nft)
nfp_nft_ct_translate_mangle_action(new_act);
nfp_nft_ct_set_flow_flag(new_act, entry);
/* Entunnel is a special case, need to allocate and copy
* tunnel info.
*/

View file

@ -103,6 +103,10 @@ enum nfp_nfp_layer_name {
_FLOW_PAY_LAYERS_MAX
};
/* NFP flow entry flags. */
#define NFP_FL_ACTION_DO_NAT BIT(0)
#define NFP_FL_ACTION_DO_MANGLE BIT(1)
/**
* struct nfp_fl_ct_flow_entry - Flow entry containing conntrack flow information
* @cookie: Flow cookie, same as original TC flow, used as key
@ -115,6 +119,7 @@ enum nfp_nfp_layer_name {
* @rule: Reference to the original TC flow rule
* @stats: Used to cache stats for updating
* @tun_offset: Used to indicate tunnel action offset in action list
* @flags: Used to indicate flow flag like NAT which used by merge.
*/
struct nfp_fl_ct_flow_entry {
unsigned long cookie;
@ -127,6 +132,7 @@ struct nfp_fl_ct_flow_entry {
struct flow_rule *rule;
struct flow_stats stats;
u8 tun_offset; // Set to NFP_FL_CT_NO_TUN if no tun
u8 flags;
};
/**

View file

@ -119,7 +119,8 @@ int nfp_flower_offload_one_police(struct nfp_app *app, bool ingress,
static int nfp_policer_validate(const struct flow_action *action,
const struct flow_action_entry *act,
struct netlink_ext_ack *extack)
struct netlink_ext_ack *extack,
bool ingress)
{
if (act->police.exceed.act_id != FLOW_ACTION_DROP) {
NL_SET_ERR_MSG_MOD(extack,
@ -127,12 +128,20 @@ static int nfp_policer_validate(const struct flow_action *action,
return -EOPNOTSUPP;
}
if (act->police.notexceed.act_id != FLOW_ACTION_CONTINUE &&
act->police.notexceed.act_id != FLOW_ACTION_PIPE &&
act->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when conform action is not continue, pipe or ok");
return -EOPNOTSUPP;
if (ingress) {
if (act->police.notexceed.act_id != FLOW_ACTION_CONTINUE &&
act->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when conform action is not continue or ok");
return -EOPNOTSUPP;
}
} else {
if (act->police.notexceed.act_id != FLOW_ACTION_PIPE &&
act->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when conform action is not pipe or ok");
return -EOPNOTSUPP;
}
}
if (act->police.notexceed.act_id == FLOW_ACTION_ACCEPT &&
@ -218,7 +227,7 @@ nfp_flower_install_rate_limiter(struct nfp_app *app, struct net_device *netdev,
return -EOPNOTSUPP;
}
err = nfp_policer_validate(&flow->rule->action, action, extack);
err = nfp_policer_validate(&flow->rule->action, action, extack, true);
if (err)
return err;
@ -687,6 +696,7 @@ nfp_act_install_actions(struct nfp_app *app, struct flow_offload_action *fl_act,
bool pps_support, pps;
bool add = false;
u64 rate;
int err;
pps_support = !!(fl_priv->flower_ext_feats & NFP_FL_FEATS_QOS_PPS);
@ -698,6 +708,11 @@ nfp_act_install_actions(struct nfp_app *app, struct flow_offload_action *fl_act,
"unsupported offload: qos rate limit offload requires police action");
continue;
}
err = nfp_policer_validate(&fl_act->action, action, extack, false);
if (err)
return err;
if (action->police.rate_bytes_ps > 0) {
rate = action->police.rate_bytes_ps;
burst = action->police.burst;