| // 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" |
| |
| const struct rhashtable_params nfp_tc_ct_merge_params = { |
| .head_offset = offsetof(struct nfp_fl_ct_tc_merge, |
| hash_node), |
| .key_len = sizeof(unsigned long) * 2, |
| .key_offset = offsetof(struct nfp_fl_ct_tc_merge, cookie), |
| .automatic_shrinking = true, |
| }; |
| |
| const struct rhashtable_params nfp_nft_ct_merge_params = { |
| .head_offset = offsetof(struct nfp_fl_nft_tc_merge, |
| hash_node), |
| .key_len = sizeof(unsigned long) * 3, |
| .key_offset = offsetof(struct nfp_fl_nft_tc_merge, cookie), |
| .automatic_shrinking = true, |
| }; |
| |
| static struct flow_action_entry *get_flow_act(struct flow_rule *rule, |
| enum flow_action_id act_id); |
| |
| /** |
| * get_hashentry() - Wrapper around hashtable lookup. |
| * @ht: hashtable where entry could be found |
| * @key: key to lookup |
| * @params: hashtable params |
| * @size: size of entry to allocate if not in table |
| * |
| * Returns an entry from a hashtable. If entry does not exist |
| * yet allocate the memory for it and return the new entry. |
| */ |
| static void *get_hashentry(struct rhashtable *ht, void *key, |
| const struct rhashtable_params params, size_t size) |
| { |
| void *result; |
| |
| result = rhashtable_lookup_fast(ht, key, params); |
| |
| if (result) |
| return result; |
| |
| result = kzalloc(size, GFP_KERNEL); |
| if (!result) |
| return ERR_PTR(-ENOMEM); |
| |
| return result; |
| } |
| |
| bool is_pre_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; |
| struct flow_match_ct ct; |
| int i; |
| |
| if (dissector->used_keys & BIT_ULL(FLOW_DISSECTOR_KEY_CT)) { |
| flow_rule_match_ct(rule, &ct); |
| if (ct.key->ct_state) |
| return false; |
| } |
| |
| if (flow->common.chain_index) |
| return false; |
| |
| flow_action_for_each(i, act, &flow->rule->action) { |
| 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; |
| } |
| |
| 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; |
| |
| if (dissector->used_keys & BIT_ULL(FLOW_DISSECTOR_KEY_CT)) { |
| flow_rule_match_ct(rule, &ct); |
| if (ct.key->ct_state & TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED) |
| return true; |
| } else { |
| /* 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; |
| } |
| } |
| /* 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; |
| } |
| |
| /** |
| * get_mangled_key() - Mangle the key if mangle act exists |
| * @rule: rule that carries the actions |
| * @buf: pointer to key to be mangled |
| * @offset: used to adjust mangled offset in L2/L3/L4 header |
| * @key_sz: key size |
| * @htype: mangling type |
| * |
| * Returns buf where the mangled key stores. |
| */ |
| static void *get_mangled_key(struct flow_rule *rule, void *buf, |
| u32 offset, size_t key_sz, |
| enum flow_action_mangle_base htype) |
| { |
| struct flow_action_entry *act; |
| u32 *val = (u32 *)buf; |
| u32 off, msk, key; |
| int i; |
| |
| flow_action_for_each(i, act, &rule->action) { |
| if (act->id == FLOW_ACTION_MANGLE && |
| act->mangle.htype == htype) { |
| off = act->mangle.offset - offset; |
| msk = act->mangle.mask; |
| key = act->mangle.val; |
| |
| /* Mangling is supposed to be u32 aligned */ |
| if (off % 4 || off >= key_sz) |
| continue; |
| |
| val[off >> 2] &= msk; |
| val[off >> 2] |= key; |
| } |
| } |
| |
| return buf; |
| } |
| |
| /* Only tos and ttl are involved in flow_match_ip structure, which |
| * doesn't conform to the layout of ip/ipv6 header definition. So |
| * they need particular process here: fill them into the ip/ipv6 |
| * header, so that mangling actions can work directly. |
| */ |
| #define NFP_IPV4_TOS_MASK GENMASK(23, 16) |
| #define NFP_IPV4_TTL_MASK GENMASK(31, 24) |
| #define NFP_IPV6_TCLASS_MASK GENMASK(27, 20) |
| #define NFP_IPV6_HLIMIT_MASK GENMASK(7, 0) |
| static void *get_mangled_tos_ttl(struct flow_rule *rule, void *buf, |
| bool is_v6) |
| { |
| struct flow_match_ip match; |
| /* IPv4's ttl field is in third dword. */ |
| __be32 ip_hdr[3]; |
| u32 tmp, hdr_len; |
| |
| flow_rule_match_ip(rule, &match); |
| |
| if (is_v6) { |
| tmp = FIELD_PREP(NFP_IPV6_TCLASS_MASK, match.key->tos); |
| ip_hdr[0] = cpu_to_be32(tmp); |
| tmp = FIELD_PREP(NFP_IPV6_HLIMIT_MASK, match.key->ttl); |
| ip_hdr[1] = cpu_to_be32(tmp); |
| hdr_len = 2 * sizeof(__be32); |
| } else { |
| tmp = FIELD_PREP(NFP_IPV4_TOS_MASK, match.key->tos); |
| ip_hdr[0] = cpu_to_be32(tmp); |
| tmp = FIELD_PREP(NFP_IPV4_TTL_MASK, match.key->ttl); |
| ip_hdr[2] = cpu_to_be32(tmp); |
| hdr_len = 3 * sizeof(__be32); |
| } |
| |
| get_mangled_key(rule, ip_hdr, 0, hdr_len, |
| is_v6 ? FLOW_ACT_MANGLE_HDR_TYPE_IP6 : |
| FLOW_ACT_MANGLE_HDR_TYPE_IP4); |
| |
| match.key = buf; |
| |
| if (is_v6) { |
| tmp = be32_to_cpu(ip_hdr[0]); |
| match.key->tos = FIELD_GET(NFP_IPV6_TCLASS_MASK, tmp); |
| tmp = be32_to_cpu(ip_hdr[1]); |
| match.key->ttl = FIELD_GET(NFP_IPV6_HLIMIT_MASK, tmp); |
| } else { |
| tmp = be32_to_cpu(ip_hdr[0]); |
| match.key->tos = FIELD_GET(NFP_IPV4_TOS_MASK, tmp); |
| tmp = be32_to_cpu(ip_hdr[2]); |
| match.key->ttl = FIELD_GET(NFP_IPV4_TTL_MASK, tmp); |
| } |
| |
| 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 |
| * we don't care. |
| */ |
| static int nfp_ct_merge_check(struct nfp_fl_ct_flow_entry *entry1, |
| struct nfp_fl_ct_flow_entry *entry2) |
| { |
| unsigned long long ovlp_keys; |
| bool out, is_v6 = false; |
| u8 ip_proto = 0; |
| ovlp_keys = entry1->rule->match.dissector->used_keys & |
| entry2->rule->match.dissector->used_keys; |
| /* Temporary buffer for mangling keys, 64 is enough to cover max |
| * struct size of key in various fields that may be mangled. |
| * Supported fields to mangle: |
| * mac_src/mac_dst(struct flow_match_eth_addrs, 12B) |
| * nw_tos/nw_ttl(struct flow_match_ip, 2B) |
| * nw_src/nw_dst(struct flow_match_ipv4/6_addrs, 32B) |
| * tp_src/tp_dst(struct flow_match_ports, 4B) |
| */ |
| char buf[64]; |
| |
| if (entry1->netdev && entry2->netdev && |
| entry1->netdev != entry2->netdev) |
| return -EINVAL; |
| |
| /* Check the overlapped fields one by one, the unmasked part |
| * should not conflict with each other. |
| */ |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL)) { |
| struct flow_match_control match1, match2; |
| |
| flow_rule_match_control(entry1->rule, &match1); |
| flow_rule_match_control(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_BASIC)) { |
| struct flow_match_basic match1, match2; |
| |
| flow_rule_match_basic(entry1->rule, &match1); |
| flow_rule_match_basic(entry2->rule, &match2); |
| |
| /* n_proto field is a must in ct-related flows, |
| * it should be either ipv4 or ipv6. |
| */ |
| is_v6 = match1.key->n_proto == htons(ETH_P_IPV6); |
| /* ip_proto field is a must when port field is cared */ |
| ip_proto = match1.key->ip_proto; |
| |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| /* 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_ULL(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); |
| flow_rule_match_ipv4_addrs(entry2->rule, &match2); |
| |
| memcpy(buf, match1.key, sizeof(*match1.key)); |
| match1.key = get_mangled_key(entry1->rule, buf, |
| offsetof(struct iphdr, saddr), |
| sizeof(*match1.key), |
| FLOW_ACT_MANGLE_HDR_TYPE_IP4); |
| |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| /* 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_ULL(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); |
| flow_rule_match_ipv6_addrs(entry2->rule, &match2); |
| |
| memcpy(buf, match1.key, sizeof(*match1.key)); |
| match1.key = get_mangled_key(entry1->rule, buf, |
| offsetof(struct ipv6hdr, saddr), |
| sizeof(*match1.key), |
| FLOW_ACT_MANGLE_HDR_TYPE_IP6); |
| |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| /* 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_ULL(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; |
| |
| flow_rule_match_ports(entry1->rule, &match1); |
| flow_rule_match_ports(entry2->rule, &match2); |
| |
| if (ip_proto == IPPROTO_UDP) |
| htype = FLOW_ACT_MANGLE_HDR_TYPE_UDP; |
| else if (ip_proto == IPPROTO_TCP) |
| htype = FLOW_ACT_MANGLE_HDR_TYPE_TCP; |
| |
| memcpy(buf, match1.key, sizeof(*match1.key)); |
| match1.key = get_mangled_key(entry1->rule, buf, 0, |
| sizeof(*match1.key), htype); |
| |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS)) { |
| struct flow_match_eth_addrs match1, match2; |
| |
| flow_rule_match_eth_addrs(entry1->rule, &match1); |
| flow_rule_match_eth_addrs(entry2->rule, &match2); |
| |
| memcpy(buf, match1.key, sizeof(*match1.key)); |
| match1.key = get_mangled_key(entry1->rule, buf, 0, |
| sizeof(*match1.key), |
| FLOW_ACT_MANGLE_HDR_TYPE_ETH); |
| |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_VLAN)) { |
| struct flow_match_vlan match1, match2; |
| |
| flow_rule_match_vlan(entry1->rule, &match1); |
| flow_rule_match_vlan(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_MPLS)) { |
| struct flow_match_mpls match1, match2; |
| |
| flow_rule_match_mpls(entry1->rule, &match1); |
| flow_rule_match_mpls(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_TCP)) { |
| struct flow_match_tcp match1, match2; |
| |
| flow_rule_match_tcp(entry1->rule, &match1); |
| flow_rule_match_tcp(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_IP)) { |
| struct flow_match_ip match1, match2; |
| |
| flow_rule_match_ip(entry1->rule, &match1); |
| flow_rule_match_ip(entry2->rule, &match2); |
| |
| match1.key = get_mangled_tos_ttl(entry1->rule, buf, is_v6); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_KEYID)) { |
| struct flow_match_enc_keyid match1, match2; |
| |
| flow_rule_match_enc_keyid(entry1->rule, &match1); |
| flow_rule_match_enc_keyid(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_IPV4_ADDRS)) { |
| struct flow_match_ipv4_addrs match1, match2; |
| |
| flow_rule_match_enc_ipv4_addrs(entry1->rule, &match1); |
| flow_rule_match_enc_ipv4_addrs(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_IPV6_ADDRS)) { |
| struct flow_match_ipv6_addrs match1, match2; |
| |
| flow_rule_match_enc_ipv6_addrs(entry1->rule, &match1); |
| flow_rule_match_enc_ipv6_addrs(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_CONTROL)) { |
| struct flow_match_control match1, match2; |
| |
| flow_rule_match_enc_control(entry1->rule, &match1); |
| flow_rule_match_enc_control(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_IP)) { |
| struct flow_match_ip match1, match2; |
| |
| flow_rule_match_enc_ip(entry1->rule, &match1); |
| flow_rule_match_enc_ip(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_OPTS)) { |
| struct flow_match_enc_opts match1, match2; |
| |
| flow_rule_match_enc_opts(entry1->rule, &match1); |
| flow_rule_match_enc_opts(entry2->rule, &match2); |
| COMPARE_UNMASKED_FIELDS(match1, match2, &out); |
| if (out) |
| goto check_failed; |
| } |
| |
| return 0; |
| |
| check_failed: |
| 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; |
| } |
| |
| /* Extra check for multiple ct-zones merge |
| * currently surpport nft entries merge check in different zones |
| */ |
| static int nfp_ct_merge_extra_check(struct nfp_fl_ct_flow_entry *nft_entry, |
| struct nfp_fl_ct_tc_merge *tc_m_entry) |
| { |
| struct nfp_fl_nft_tc_merge *prev_nft_m_entry; |
| struct nfp_fl_ct_flow_entry *pre_ct_entry; |
| |
| pre_ct_entry = tc_m_entry->pre_ct_parent; |
| prev_nft_m_entry = pre_ct_entry->prev_m_entries[pre_ct_entry->num_prev_m_entries - 1]; |
| |
| return nfp_ct_merge_check(prev_nft_m_entry->nft_parent, nft_entry); |
| } |
| |
| 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, err; |
| |
| /* Check for pre_ct->action conflicts */ |
| flow_action_for_each(i, act, &pre_ct_entry->rule->action) { |
| switch (act->id) { |
| 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: |
| return -EOPNOTSUPP; |
| default: |
| break; |
| } |
| } |
| |
| /* Check for nft->action conflicts */ |
| flow_action_for_each(i, act, &nft_entry->rule->action) { |
| switch (act->id) { |
| case FLOW_ACTION_VLAN_PUSH: |
| case FLOW_ACTION_VLAN_POP: |
| case FLOW_ACTION_VLAN_MANGLE: |
| case FLOW_ACTION_MPLS_PUSH: |
| case FLOW_ACTION_MPLS_POP: |
| case FLOW_ACTION_MPLS_MANGLE: |
| return -EOPNOTSUPP; |
| default: |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| static int nfp_ct_check_meta(struct nfp_fl_ct_flow_entry *post_ct_entry, |
| struct nfp_fl_ct_flow_entry *nft_entry) |
| { |
| struct flow_dissector *dissector = post_ct_entry->rule->match.dissector; |
| struct flow_action_entry *ct_met; |
| struct flow_match_ct ct; |
| int i; |
| |
| ct_met = get_flow_act(nft_entry->rule, FLOW_ACTION_CT_METADATA); |
| if (ct_met && (dissector->used_keys & BIT_ULL(FLOW_DISSECTOR_KEY_CT))) { |
| u32 *act_lbl; |
| |
| act_lbl = ct_met->ct_metadata.labels; |
| flow_rule_match_ct(post_ct_entry->rule, &ct); |
| for (i = 0; i < 4; i++) { |
| if ((ct.key->ct_labels[i] & ct.mask->ct_labels[i]) ^ |
| (act_lbl[i] & ct.mask->ct_labels[i])) |
| return -EINVAL; |
| } |
| |
| if ((ct.key->ct_mark & ct.mask->ct_mark) ^ |
| (ct_met->ct_metadata.mark & ct.mask->ct_mark)) |
| 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; |
| } |
| |
| static int |
| nfp_fl_calc_key_layers_sz(struct nfp_fl_key_ls in_key_ls, uint16_t *map) |
| { |
| int key_size; |
| |
| /* This field must always be present */ |
| key_size = sizeof(struct nfp_flower_meta_tci); |
| map[FLOW_PAY_META_TCI] = 0; |
| |
| if (in_key_ls.key_layer & NFP_FLOWER_LAYER_EXT_META) { |
| map[FLOW_PAY_EXT_META] = key_size; |
| key_size += sizeof(struct nfp_flower_ext_meta); |
| } |
| if (in_key_ls.key_layer & NFP_FLOWER_LAYER_PORT) { |
| map[FLOW_PAY_INPORT] = key_size; |
| key_size += sizeof(struct nfp_flower_in_port); |
| } |
| if (in_key_ls.key_layer & NFP_FLOWER_LAYER_MAC) { |
| map[FLOW_PAY_MAC_MPLS] = key_size; |
| key_size += sizeof(struct nfp_flower_mac_mpls); |
| } |
| if (in_key_ls.key_layer & NFP_FLOWER_LAYER_TP) { |
| map[FLOW_PAY_L4] = key_size; |
| key_size += sizeof(struct nfp_flower_tp_ports); |
| } |
| if (in_key_ls.key_layer & NFP_FLOWER_LAYER_IPV4) { |
| map[FLOW_PAY_IPV4] = key_size; |
| key_size += sizeof(struct nfp_flower_ipv4); |
| } |
| if (in_key_ls.key_layer & NFP_FLOWER_LAYER_IPV6) { |
| map[FLOW_PAY_IPV6] = key_size; |
| key_size += sizeof(struct nfp_flower_ipv6); |
| } |
| |
| if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_QINQ) { |
| map[FLOW_PAY_QINQ] = key_size; |
| key_size += sizeof(struct nfp_flower_vlan); |
| } |
| |
| if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_GRE) { |
| map[FLOW_PAY_GRE] = key_size; |
| if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_TUN_IPV6) |
| key_size += sizeof(struct nfp_flower_ipv6_gre_tun); |
| else |
| key_size += sizeof(struct nfp_flower_ipv4_gre_tun); |
| } |
| |
| if ((in_key_ls.key_layer & NFP_FLOWER_LAYER_VXLAN) || |
| (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_GENEVE)) { |
| map[FLOW_PAY_UDP_TUN] = key_size; |
| if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_TUN_IPV6) |
| key_size += sizeof(struct nfp_flower_ipv6_udp_tun); |
| else |
| key_size += sizeof(struct nfp_flower_ipv4_udp_tun); |
| } |
| |
| if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_GENEVE_OP) { |
| map[FLOW_PAY_GENEVE_OPT] = key_size; |
| key_size += sizeof(struct nfp_flower_geneve_options); |
| } |
| |
| 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, |
| int num_rules) |
| { |
| enum flow_action_hw_stats tmp_stats = FLOW_ACTION_HW_STATS_DONT_CARE; |
| struct flow_action_entry *a_in; |
| int i, j, id, num_actions = 0; |
| struct flow_rule *a_rule; |
| int err = 0, offset = 0; |
| |
| for (i = 0; i < num_rules; i++) |
| num_actions += rules[i]->action.num_entries; |
| |
| /* 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 + (num_rules / 2)); |
| if (!a_rule) |
| return -ENOMEM; |
| |
| /* post_ct entry have one action at least. */ |
| if (rules[num_rules - 1]->action.num_entries != 0) |
| tmp_stats = rules[num_rules - 1]->action.entries[0].hw_stats; |
| |
| /* Actions need a BASIC dissector. */ |
| a_rule->match = rules[0]->match; |
| |
| /* Copy actions */ |
| for (j = 0; j < num_rules; 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; |
| |
| /* ip_proto is the only field that is needed in later compile_action, |
| * needed to set the correct checksum flags. It doesn't really matter |
| * which input rule's ip_proto field we take as the earlier merge checks |
| * would have made sure that they don't conflict. We do not know which |
| * of the subflows would have the ip_proto filled in, so we need to iterate |
| * through the subflows and assign the proper subflow to a_rule |
| */ |
| flow_rule_match_basic(rules[j], &match); |
| 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++) { |
| a_in = &rules[j]->action.entries[i]; |
| id = a_in->id; |
| |
| /* Ignore CT related actions as these would already have |
| * been taken care of by previous checks, and we do not send |
| * any CT actions to the firmware. |
| */ |
| switch (id) { |
| case FLOW_ACTION_CT: |
| case FLOW_ACTION_GOTO: |
| 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. |
| * nft entry flow rules are at odd array index. |
| */ |
| if (j & 0x01) { |
| 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 */ |
| a_rule->action.num_entries = offset; |
| err = nfp_flower_compile_action(priv->app, a_rule, netdev, flow_pay, NULL); |
| kfree(a_rule); |
| |
| return err; |
| } |
| |
| static int nfp_fl_ct_add_offload(struct nfp_fl_nft_tc_merge *m_entry) |
| { |
| enum nfp_flower_tun_type tun_type = NFP_FL_TUNNEL_NONE; |
| struct nfp_fl_ct_zone_entry *zt = m_entry->zt; |
| struct flow_rule *rules[NFP_MAX_ENTRY_RULES]; |
| struct nfp_fl_ct_flow_entry *pre_ct_entry; |
| struct nfp_fl_key_ls key_layer, tmp_layer; |
| struct nfp_flower_priv *priv = zt->priv; |
| u16 key_map[_FLOW_PAY_LAYERS_MAX]; |
| struct nfp_fl_payload *flow_pay; |
| u8 *key, *msk, *kdata, *mdata; |
| struct nfp_port *port = NULL; |
| int num_rules, err, i, j = 0; |
| struct net_device *netdev; |
| bool qinq_sup; |
| u32 port_id; |
| u16 offset; |
| |
| netdev = m_entry->netdev; |
| qinq_sup = !!(priv->flower_ext_feats & NFP_FL_FEATS_VLAN_QINQ); |
| |
| pre_ct_entry = m_entry->tc_m_parent->pre_ct_parent; |
| num_rules = pre_ct_entry->num_prev_m_entries * 2 + _CT_TYPE_MAX; |
| |
| for (i = 0; i < pre_ct_entry->num_prev_m_entries; i++) { |
| rules[j++] = pre_ct_entry->prev_m_entries[i]->tc_m_parent->pre_ct_parent->rule; |
| rules[j++] = pre_ct_entry->prev_m_entries[i]->nft_parent->rule; |
| } |
| |
| rules[j++] = m_entry->tc_m_parent->pre_ct_parent->rule; |
| rules[j++] = m_entry->nft_parent->rule; |
| rules[j++] = m_entry->tc_m_parent->post_ct_parent->rule; |
| |
| memset(&key_layer, 0, sizeof(struct nfp_fl_key_ls)); |
| memset(&key_map, 0, sizeof(key_map)); |
| |
| /* Calculate the resultant key layer and size for offload */ |
| for (i = 0; i < num_rules; i++) { |
| err = nfp_flower_calculate_key_layers(priv->app, |
| m_entry->netdev, |
| &tmp_layer, rules[i], |
| &tun_type, NULL); |
| if (err) |
| return err; |
| |
| key_layer.key_layer |= tmp_layer.key_layer; |
| key_layer.key_layer_two |= tmp_layer.key_layer_two; |
| } |
| key_layer.key_size = nfp_fl_calc_key_layers_sz(key_layer, key_map); |
| |
| flow_pay = nfp_flower_allocate_new(&key_layer); |
| if (!flow_pay) |
| return -ENOMEM; |
| |
| memset(flow_pay->unmasked_data, 0, key_layer.key_size); |
| memset(flow_pay->mask_data, 0, key_layer.key_size); |
| |
| kdata = flow_pay->unmasked_data; |
| mdata = flow_pay->mask_data; |
| |
| offset = key_map[FLOW_PAY_META_TCI]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| nfp_flower_compile_meta((struct nfp_flower_meta_tci *)key, |
| (struct nfp_flower_meta_tci *)msk, |
| key_layer.key_layer); |
| |
| if (NFP_FLOWER_LAYER_EXT_META & key_layer.key_layer) { |
| offset = key_map[FLOW_PAY_EXT_META]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| nfp_flower_compile_ext_meta((struct nfp_flower_ext_meta *)key, |
| key_layer.key_layer_two); |
| nfp_flower_compile_ext_meta((struct nfp_flower_ext_meta *)msk, |
| key_layer.key_layer_two); |
| } |
| |
| /* Using in_port from the -trk rule. The tc merge checks should already |
| * be checking that the ingress netdevs are the same |
| */ |
| port_id = nfp_flower_get_port_id_from_netdev(priv->app, netdev); |
| offset = key_map[FLOW_PAY_INPORT]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| err = nfp_flower_compile_port((struct nfp_flower_in_port *)key, |
| port_id, false, tun_type, NULL); |
| if (err) |
| goto ct_offload_err; |
| err = nfp_flower_compile_port((struct nfp_flower_in_port *)msk, |
| port_id, true, tun_type, NULL); |
| if (err) |
| goto ct_offload_err; |
| |
| /* This following part works on the assumption that previous checks has |
| * already filtered out flows that has different values for the different |
| * layers. Here we iterate through all three rules and merge their respective |
| * masked value(cared bits), basic method is: |
| * final_key = (r1_key & r1_mask) | (r2_key & r2_mask) | (r3_key & r3_mask) |
| * final_mask = r1_mask | r2_mask | r3_mask |
| * If none of the rules contains a match that is also fine, that simply means |
| * that the layer is not present. |
| */ |
| if (!qinq_sup) { |
| for (i = 0; i < num_rules; i++) { |
| offset = key_map[FLOW_PAY_META_TCI]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| nfp_flower_compile_tci((struct nfp_flower_meta_tci *)key, |
| (struct nfp_flower_meta_tci *)msk, |
| rules[i]); |
| } |
| } |
| |
| if (NFP_FLOWER_LAYER_MAC & key_layer.key_layer) { |
| offset = key_map[FLOW_PAY_MAC_MPLS]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| for (i = 0; i < num_rules; i++) { |
| nfp_flower_compile_mac((struct nfp_flower_mac_mpls *)key, |
| (struct nfp_flower_mac_mpls *)msk, |
| rules[i]); |
| err = nfp_flower_compile_mpls((struct nfp_flower_mac_mpls *)key, |
| (struct nfp_flower_mac_mpls *)msk, |
| rules[i], NULL); |
| if (err) |
| goto ct_offload_err; |
| } |
| } |
| |
| if (NFP_FLOWER_LAYER_IPV4 & key_layer.key_layer) { |
| offset = key_map[FLOW_PAY_IPV4]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| for (i = 0; i < num_rules; i++) { |
| nfp_flower_compile_ipv4((struct nfp_flower_ipv4 *)key, |
| (struct nfp_flower_ipv4 *)msk, |
| rules[i]); |
| } |
| } |
| |
| if (NFP_FLOWER_LAYER_IPV6 & key_layer.key_layer) { |
| offset = key_map[FLOW_PAY_IPV6]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| for (i = 0; i < num_rules; i++) { |
| nfp_flower_compile_ipv6((struct nfp_flower_ipv6 *)key, |
| (struct nfp_flower_ipv6 *)msk, |
| rules[i]); |
| } |
| } |
| |
| if (NFP_FLOWER_LAYER_TP & key_layer.key_layer) { |
| offset = key_map[FLOW_PAY_L4]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| for (i = 0; i < num_rules; i++) { |
| nfp_flower_compile_tport((struct nfp_flower_tp_ports *)key, |
| (struct nfp_flower_tp_ports *)msk, |
| rules[i]); |
| } |
| } |
| |
| if (NFP_FLOWER_LAYER2_QINQ & key_layer.key_layer_two) { |
| offset = key_map[FLOW_PAY_QINQ]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| for (i = 0; i < num_rules; i++) { |
| nfp_flower_compile_vlan((struct nfp_flower_vlan *)key, |
| (struct nfp_flower_vlan *)msk, |
| rules[i]); |
| } |
| } |
| |
| if (key_layer.key_layer_two & NFP_FLOWER_LAYER2_GRE) { |
| offset = key_map[FLOW_PAY_GRE]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| if (key_layer.key_layer_two & NFP_FLOWER_LAYER2_TUN_IPV6) { |
| struct nfp_flower_ipv6_gre_tun *gre_match; |
| struct nfp_ipv6_addr_entry *entry; |
| struct in6_addr *dst; |
| |
| for (i = 0; i < num_rules; i++) { |
| nfp_flower_compile_ipv6_gre_tun((void *)key, |
| (void *)msk, rules[i]); |
| } |
| gre_match = (struct nfp_flower_ipv6_gre_tun *)key; |
| dst = &gre_match->ipv6.dst; |
| |
| entry = nfp_tunnel_add_ipv6_off(priv->app, dst); |
| if (!entry) { |
| err = -ENOMEM; |
| goto ct_offload_err; |
| } |
| |
| flow_pay->nfp_tun_ipv6 = entry; |
| } else { |
| __be32 dst; |
| |
| for (i = 0; i < num_rules; i++) { |
| nfp_flower_compile_ipv4_gre_tun((void *)key, |
| (void *)msk, rules[i]); |
| } |
| dst = ((struct nfp_flower_ipv4_gre_tun *)key)->ipv4.dst; |
| |
| /* Store the tunnel destination in the rule data. |
| * This must be present and be an exact match. |
| */ |
| flow_pay->nfp_tun_ipv4_addr = dst; |
| nfp_tunnel_add_ipv4_off(priv->app, dst); |
| } |
| } |
| |
| if (key_layer.key_layer & NFP_FLOWER_LAYER_VXLAN || |
| key_layer.key_layer_two & NFP_FLOWER_LAYER2_GENEVE) { |
| offset = key_map[FLOW_PAY_UDP_TUN]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| if (key_layer.key_layer_two & NFP_FLOWER_LAYER2_TUN_IPV6) { |
| struct nfp_flower_ipv6_udp_tun *udp_match; |
| struct nfp_ipv6_addr_entry *entry; |
| struct in6_addr *dst; |
| |
| for (i = 0; i < num_rules; i++) { |
| nfp_flower_compile_ipv6_udp_tun((void *)key, |
| (void *)msk, rules[i]); |
| } |
| udp_match = (struct nfp_flower_ipv6_udp_tun *)key; |
| dst = &udp_match->ipv6.dst; |
| |
| entry = nfp_tunnel_add_ipv6_off(priv->app, dst); |
| if (!entry) { |
| err = -ENOMEM; |
| goto ct_offload_err; |
| } |
| |
| flow_pay->nfp_tun_ipv6 = entry; |
| } else { |
| __be32 dst; |
| |
| for (i = 0; i < num_rules; i++) { |
| nfp_flower_compile_ipv4_udp_tun((void *)key, |
| (void *)msk, rules[i]); |
| } |
| dst = ((struct nfp_flower_ipv4_udp_tun *)key)->ipv4.dst; |
| |
| /* Store the tunnel destination in the rule data. |
| * This must be present and be an exact match. |
| */ |
| flow_pay->nfp_tun_ipv4_addr = dst; |
| nfp_tunnel_add_ipv4_off(priv->app, dst); |
| } |
| |
| if (key_layer.key_layer_two & NFP_FLOWER_LAYER2_GENEVE_OP) { |
| offset = key_map[FLOW_PAY_GENEVE_OPT]; |
| key = kdata + offset; |
| msk = mdata + offset; |
| for (i = 0; i < num_rules; i++) |
| nfp_flower_compile_geneve_opt(key, msk, rules[i]); |
| } |
| } |
| |
| /* Merge actions into flow_pay */ |
| err = nfp_fl_merge_actions_offload(rules, priv, netdev, flow_pay, num_rules); |
| if (err) |
| goto ct_offload_err; |
| |
| /* Use the pointer address as the cookie, but set the last bit to 1. |
| * This is to avoid the 'is_merge_flow' check from detecting this as |
| * an already merged flow. This works since address alignment means |
| * that the last bit for pointer addresses will be 0. |
| */ |
| flow_pay->tc_flower_cookie = ((unsigned long)flow_pay) | 0x1; |
| err = nfp_compile_flow_metadata(priv->app, flow_pay->tc_flower_cookie, |
| flow_pay, netdev, NULL); |
| if (err) |
| goto ct_offload_err; |
| |
| if (nfp_netdev_is_nfp_repr(netdev)) |
| port = nfp_port_from_netdev(netdev); |
| |
| err = rhashtable_insert_fast(&priv->flow_table, &flow_pay->fl_node, |
| nfp_flower_table_params); |
| if (err) |
| goto ct_release_offload_meta_err; |
| |
| err = nfp_flower_xmit_flow(priv->app, flow_pay, |
| NFP_FLOWER_CMSG_TYPE_FLOW_ADD); |
| if (err) |
| goto ct_remove_rhash_err; |
| |
| m_entry->tc_flower_cookie = flow_pay->tc_flower_cookie; |
| m_entry->flow_pay = flow_pay; |
| |
| if (port) |
| port->tc_offload_cnt++; |
| |
| return err; |
| |
| ct_remove_rhash_err: |
| WARN_ON_ONCE(rhashtable_remove_fast(&priv->flow_table, |
| &flow_pay->fl_node, |
| nfp_flower_table_params)); |
| ct_release_offload_meta_err: |
| nfp_modify_flow_metadata(priv->app, flow_pay); |
| ct_offload_err: |
| if (flow_pay->nfp_tun_ipv4_addr) |
| nfp_tunnel_del_ipv4_off(priv->app, flow_pay->nfp_tun_ipv4_addr); |
| if (flow_pay->nfp_tun_ipv6) |
| nfp_tunnel_put_ipv6_off(priv->app, flow_pay->nfp_tun_ipv6); |
| kfree(flow_pay->action_data); |
| kfree(flow_pay->mask_data); |
| kfree(flow_pay->unmasked_data); |
| kfree(flow_pay); |
| return err; |
| } |
| |
| static int nfp_fl_ct_del_offload(struct nfp_app *app, unsigned long cookie, |
| struct net_device *netdev) |
| { |
| struct nfp_flower_priv *priv = app->priv; |
| struct nfp_fl_payload *flow_pay; |
| struct nfp_port *port = NULL; |
| int err = 0; |
| |
| if (nfp_netdev_is_nfp_repr(netdev)) |
| port = nfp_port_from_netdev(netdev); |
| |
| flow_pay = nfp_flower_search_fl_table(app, cookie, netdev); |
| if (!flow_pay) |
| return -ENOENT; |
| |
| err = nfp_modify_flow_metadata(app, flow_pay); |
| if (err) |
| goto err_free_merge_flow; |
| |
| if (flow_pay->nfp_tun_ipv4_addr) |
| nfp_tunnel_del_ipv4_off(app, flow_pay->nfp_tun_ipv4_addr); |
| |
| if (flow_pay->nfp_tun_ipv6) |
| nfp_tunnel_put_ipv6_off(app, flow_pay->nfp_tun_ipv6); |
| |
| if (!flow_pay->in_hw) { |
| err = 0; |
| goto err_free_merge_flow; |
| } |
| |
| err = nfp_flower_xmit_flow(app, flow_pay, |
| NFP_FLOWER_CMSG_TYPE_FLOW_DEL); |
| |
| err_free_merge_flow: |
| nfp_flower_del_linked_merge_flows(app, flow_pay); |
| if (port) |
| port->tc_offload_cnt--; |
| kfree(flow_pay->action_data); |
| kfree(flow_pay->mask_data); |
| kfree(flow_pay->unmasked_data); |
| WARN_ON_ONCE(rhashtable_remove_fast(&priv->flow_table, |
| &flow_pay->fl_node, |
| nfp_flower_table_params)); |
| kfree_rcu(flow_pay, rcu); |
| return err; |
| } |
| |
| static int nfp_ct_do_nft_merge(struct nfp_fl_ct_zone_entry *zt, |
| struct nfp_fl_ct_flow_entry *nft_entry, |
| struct nfp_fl_ct_tc_merge *tc_m_entry) |
| { |
| struct nfp_fl_ct_flow_entry *post_ct_entry, *pre_ct_entry; |
| struct nfp_fl_nft_tc_merge *nft_m_entry; |
| unsigned long new_cookie[3]; |
| int err; |
| |
| pre_ct_entry = tc_m_entry->pre_ct_parent; |
| post_ct_entry = tc_m_entry->post_ct_parent; |
| |
| err = nfp_ct_merge_act_check(pre_ct_entry, post_ct_entry, nft_entry); |
| if (err) |
| return err; |
| |
| /* Check that the two tc flows are also compatible with |
| * the nft entry. No need to check the pre_ct and post_ct |
| * entries as that was already done during pre_merge. |
| * The nft entry does not have a chain populated, so |
| * skip this check. |
| */ |
| err = nfp_ct_merge_check(pre_ct_entry, nft_entry); |
| if (err) |
| return err; |
| err = nfp_ct_merge_check(nft_entry, post_ct_entry); |
| if (err) |
| return err; |
| err = nfp_ct_check_meta(post_ct_entry, nft_entry); |
| if (err) |
| return err; |
| |
| if (pre_ct_entry->num_prev_m_entries > 0) { |
| err = nfp_ct_merge_extra_check(nft_entry, tc_m_entry); |
| if (err) |
| return err; |
| } |
| |
| /* Combine tc_merge and nft cookies for this cookie. */ |
| new_cookie[0] = tc_m_entry->cookie[0]; |
| new_cookie[1] = tc_m_entry->cookie[1]; |
| new_cookie[2] = nft_entry->cookie; |
| nft_m_entry = get_hashentry(&zt->nft_merge_tb, |
| &new_cookie, |
| nfp_nft_ct_merge_params, |
| sizeof(*nft_m_entry)); |
| |
| if (IS_ERR(nft_m_entry)) |
| return PTR_ERR(nft_m_entry); |
| |
| /* nft_m_entry already present, not merging again */ |
| if (!memcmp(&new_cookie, nft_m_entry->cookie, sizeof(new_cookie))) |
| return 0; |
| |
| memcpy(&nft_m_entry->cookie, &new_cookie, sizeof(new_cookie)); |
| nft_m_entry->zt = zt; |
| nft_m_entry->tc_m_parent = tc_m_entry; |
| nft_m_entry->nft_parent = nft_entry; |
| nft_m_entry->tc_flower_cookie = 0; |
| /* Copy the netdev from the pre_ct entry. When the tc_m_entry was created |
| * it only combined them if the netdevs were the same, so can use any of them. |
| */ |
| nft_m_entry->netdev = pre_ct_entry->netdev; |
| |
| /* Add this entry to the tc_m_list and nft_flow lists */ |
| list_add(&nft_m_entry->tc_merge_list, &tc_m_entry->children); |
| list_add(&nft_m_entry->nft_flow_list, &nft_entry->children); |
| |
| err = rhashtable_insert_fast(&zt->nft_merge_tb, &nft_m_entry->hash_node, |
| nfp_nft_ct_merge_params); |
| if (err) |
| goto err_nft_ct_merge_insert; |
| |
| zt->nft_merge_count++; |
| |
| if (post_ct_entry->goto_chain_index > 0) |
| return nfp_fl_create_new_pre_ct(nft_m_entry); |
| |
| /* Generate offload structure and send to nfp */ |
| err = nfp_fl_ct_add_offload(nft_m_entry); |
| if (err) |
| goto err_nft_ct_offload; |
| |
| return err; |
| |
| err_nft_ct_offload: |
| nfp_fl_ct_del_offload(zt->priv->app, nft_m_entry->tc_flower_cookie, |
| nft_m_entry->netdev); |
| err_nft_ct_merge_insert: |
| list_del(&nft_m_entry->tc_merge_list); |
| list_del(&nft_m_entry->nft_flow_list); |
| kfree(nft_m_entry); |
| return err; |
| } |
| |
| static int nfp_ct_do_tc_merge(struct nfp_fl_ct_zone_entry *zt, |
| struct nfp_fl_ct_flow_entry *ct_entry1, |
| struct nfp_fl_ct_flow_entry *ct_entry2) |
| { |
| struct nfp_fl_ct_flow_entry *post_ct_entry, *pre_ct_entry; |
| struct nfp_fl_ct_flow_entry *nft_entry, *nft_tmp; |
| struct nfp_fl_ct_tc_merge *m_entry; |
| unsigned long new_cookie[2]; |
| int err; |
| |
| if (ct_entry1->type == CT_TYPE_PRE_CT) { |
| pre_ct_entry = ct_entry1; |
| post_ct_entry = ct_entry2; |
| } else { |
| post_ct_entry = ct_entry1; |
| pre_ct_entry = ct_entry2; |
| } |
| |
| /* Checks that the chain_index of the filter matches the |
| * chain_index of the GOTO action. |
| */ |
| if (post_ct_entry->chain_index != pre_ct_entry->goto_chain_index) |
| return -EINVAL; |
| |
| err = nfp_ct_merge_check(pre_ct_entry, post_ct_entry); |
| if (err) |
| return err; |
| |
| new_cookie[0] = pre_ct_entry->cookie; |
| new_cookie[1] = post_ct_entry->cookie; |
| m_entry = get_hashentry(&zt->tc_merge_tb, &new_cookie, |
| nfp_tc_ct_merge_params, sizeof(*m_entry)); |
| if (IS_ERR(m_entry)) |
| return PTR_ERR(m_entry); |
| |
| /* m_entry already present, not merging again */ |
| if (!memcmp(&new_cookie, m_entry->cookie, sizeof(new_cookie))) |
| return 0; |
| |
| memcpy(&m_entry->cookie, &new_cookie, sizeof(new_cookie)); |
| m_entry->zt = zt; |
| m_entry->post_ct_parent = post_ct_entry; |
| m_entry->pre_ct_parent = pre_ct_entry; |
| |
| /* Add this entry to the pre_ct and post_ct lists */ |
| list_add(&m_entry->post_ct_list, &post_ct_entry->children); |
| list_add(&m_entry->pre_ct_list, &pre_ct_entry->children); |
| INIT_LIST_HEAD(&m_entry->children); |
| |
| err = rhashtable_insert_fast(&zt->tc_merge_tb, &m_entry->hash_node, |
| nfp_tc_ct_merge_params); |
| if (err) |
| goto err_ct_tc_merge_insert; |
| zt->tc_merge_count++; |
| |
| /* Merge with existing nft flows */ |
| list_for_each_entry_safe(nft_entry, nft_tmp, &zt->nft_flows_list, |
| list_node) { |
| nfp_ct_do_nft_merge(zt, nft_entry, m_entry); |
| } |
| |
| return 0; |
| |
| err_ct_tc_merge_insert: |
| list_del(&m_entry->post_ct_list); |
| list_del(&m_entry->pre_ct_list); |
| kfree(m_entry); |
| return err; |
| } |
| |
| static struct |
| nfp_fl_ct_zone_entry *get_nfp_zone_entry(struct nfp_flower_priv *priv, |
| u16 zone, bool wildcarded) |
| { |
| struct nfp_fl_ct_zone_entry *zt; |
| int err; |
| |
| if (wildcarded && priv->ct_zone_wc) |
| return priv->ct_zone_wc; |
| |
| if (!wildcarded) { |
| zt = get_hashentry(&priv->ct_zone_table, &zone, |
| nfp_zone_table_params, sizeof(*zt)); |
| |
| /* If priv is set this is an existing entry, just return it */ |
| if (IS_ERR(zt) || zt->priv) |
| return zt; |
| } else { |
| zt = kzalloc(sizeof(*zt), GFP_KERNEL); |
| if (!zt) |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| zt->zone = zone; |
| zt->priv = priv; |
| zt->nft = NULL; |
| |
| /* init the various hash tables and lists */ |
| INIT_LIST_HEAD(&zt->pre_ct_list); |
| INIT_LIST_HEAD(&zt->post_ct_list); |
| INIT_LIST_HEAD(&zt->nft_flows_list); |
| |
| err = rhashtable_init(&zt->tc_merge_tb, &nfp_tc_ct_merge_params); |
| if (err) |
| goto err_tc_merge_tb_init; |
| |
| err = rhashtable_init(&zt->nft_merge_tb, &nfp_nft_ct_merge_params); |
| if (err) |
| goto err_nft_merge_tb_init; |
| |
| if (wildcarded) { |
| priv->ct_zone_wc = zt; |
| } else { |
| err = rhashtable_insert_fast(&priv->ct_zone_table, |
| &zt->hash_node, |
| nfp_zone_table_params); |
| if (err) |
| goto err_zone_insert; |
| } |
| |
| return zt; |
| |
| err_zone_insert: |
| rhashtable_destroy(&zt->nft_merge_tb); |
| err_nft_merge_tb_init: |
| rhashtable_destroy(&zt->tc_merge_tb); |
| err_tc_merge_tb_init: |
| kfree(zt); |
| return ERR_PTR(err); |
| } |
| |
| static struct net_device *get_netdev_from_rule(struct flow_rule *rule) |
| { |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_META)) { |
| struct flow_match_meta match; |
| |
| flow_rule_match_meta(rule, &match); |
| if (match.key->ingress_ifindex & match.mask->ingress_ifindex) |
| return __dev_get_by_index(&init_net, |
| match.key->ingress_ifindex); |
| } |
| |
| 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; |
| |
| /* Both struct tcphdr and struct udphdr start with |
| * __be16 source; |
| * __be16 dest; |
| * so we can use the same code for both. |
| */ |
| case FLOW_ACT_MANGLE_HDR_TYPE_TCP: |
| case FLOW_ACT_MANGLE_HDR_TYPE_UDP: |
| if (mangle_action->mangle.offset == offsetof(struct tcphdr, source)) { |
| mangle_action->mangle.val = |
| (__force u32)cpu_to_be32(mangle_action->mangle.val << 16); |
| /* The mask of mangle action is inverse mask, |
| * so clear the dest tp port with 0xFFFF to |
| * instead of rotate-left operation. |
| */ |
| mangle_action->mangle.mask = |
| (__force u32)cpu_to_be32(mangle_action->mangle.mask << 16 | 0xFFFF); |
| } |
| if (mangle_action->mangle.offset == offsetof(struct tcphdr, dest)) { |
| mangle_action->mangle.offset = 0; |
| 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; |
| |
| 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, |
| struct flow_cls_offload *flow, |
| bool is_nft, struct netlink_ext_ack *extack) |
| { |
| struct nf_flow_match *nft_match = NULL; |
| struct nfp_fl_ct_flow_entry *entry; |
| struct nfp_fl_ct_map_entry *map; |
| struct flow_action_entry *act; |
| int err, i; |
| |
| entry = kzalloc(sizeof(*entry), GFP_KERNEL); |
| if (!entry) |
| return ERR_PTR(-ENOMEM); |
| |
| entry->rule = flow_rule_alloc(flow->rule->action.num_entries); |
| if (!entry->rule) { |
| err = -ENOMEM; |
| goto err_pre_ct_rule; |
| } |
| |
| /* nft flows gets destroyed after callback return, so need |
| * to do a full copy instead of just a reference. |
| */ |
| if (is_nft) { |
| nft_match = kzalloc(sizeof(*nft_match), GFP_KERNEL); |
| if (!nft_match) { |
| err = -ENOMEM; |
| goto err_pre_ct_act; |
| } |
| memcpy(&nft_match->dissector, flow->rule->match.dissector, |
| sizeof(nft_match->dissector)); |
| memcpy(&nft_match->mask, flow->rule->match.mask, |
| sizeof(nft_match->mask)); |
| memcpy(&nft_match->key, flow->rule->match.key, |
| sizeof(nft_match->key)); |
| entry->rule->match.dissector = &nft_match->dissector; |
| entry->rule->match.mask = &nft_match->mask; |
| entry->rule->match.key = &nft_match->key; |
| |
| if (!netdev) |
| netdev = get_netdev_from_rule(entry->rule); |
| } else { |
| entry->rule->match.dissector = flow->rule->match.dissector; |
| entry->rule->match.mask = flow->rule->match.mask; |
| entry->rule->match.key = flow->rule->match.key; |
| } |
| |
| entry->zt = zt; |
| entry->netdev = netdev; |
| entry->cookie = flow->cookie > 0 ? flow->cookie : (unsigned long)entry; |
| entry->chain_index = flow->common.chain_index; |
| entry->tun_offset = NFP_FL_CT_NO_TUN; |
| |
| /* Copy over action data. Unfortunately we do not get a handle to the |
| * original tcf_action data, and the flow objects gets destroyed, so we |
| * cannot just save a pointer to this either, so need to copy over the |
| * data unfortunately. |
| */ |
| entry->rule->action.num_entries = flow->rule->action.num_entries; |
| flow_action_for_each(i, act, &flow->rule->action) { |
| struct flow_action_entry *new_act; |
| |
| 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. |
| */ |
| if (act->id == FLOW_ACTION_TUNNEL_ENCAP) { |
| struct ip_tunnel_info *tun = act->tunnel; |
| size_t tun_size = sizeof(*tun) + tun->options_len; |
| |
| new_act->tunnel = kmemdup(tun, tun_size, GFP_ATOMIC); |
| if (!new_act->tunnel) { |
| err = -ENOMEM; |
| goto err_pre_ct_tun_cp; |
| } |
| entry->tun_offset = i; |
| } |
| } |
| |
| INIT_LIST_HEAD(&entry->children); |
| |
| if (flow->cookie == 0) |
| return entry; |
| |
| /* Now add a ct map entry to flower-priv */ |
| map = get_hashentry(&zt->priv->ct_map_table, &flow->cookie, |
| nfp_ct_map_params, sizeof(*map)); |
| if (IS_ERR(map)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "offload error: ct map entry creation failed"); |
| err = -ENOMEM; |
| goto err_ct_flow_insert; |
| } |
| map->cookie = flow->cookie; |
| map->ct_entry = entry; |
| err = rhashtable_insert_fast(&zt->priv->ct_map_table, |
| &map->hash_node, |
| nfp_ct_map_params); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "offload error: ct map entry table add failed"); |
| goto err_map_insert; |
| } |
| |
| return entry; |
| |
| err_map_insert: |
| kfree(map); |
| err_ct_flow_insert: |
| if (entry->tun_offset != NFP_FL_CT_NO_TUN) |
| kfree(entry->rule->action.entries[entry->tun_offset].tunnel); |
| err_pre_ct_tun_cp: |
| kfree(nft_match); |
| err_pre_ct_act: |
| kfree(entry->rule); |
| err_pre_ct_rule: |
| kfree(entry); |
| return ERR_PTR(err); |
| } |
| |
| static void cleanup_nft_merge_entry(struct nfp_fl_nft_tc_merge *m_entry) |
| { |
| struct nfp_fl_ct_zone_entry *zt; |
| int err; |
| |
| zt = m_entry->zt; |
| |
| /* Flow is in HW, need to delete */ |
| if (m_entry->tc_flower_cookie) { |
| err = nfp_fl_ct_del_offload(zt->priv->app, m_entry->tc_flower_cookie, |
| m_entry->netdev); |
| if (err) |
| return; |
| } |
| |
| WARN_ON_ONCE(rhashtable_remove_fast(&zt->nft_merge_tb, |
| &m_entry->hash_node, |
| nfp_nft_ct_merge_params)); |
| zt->nft_merge_count--; |
| list_del(&m_entry->tc_merge_list); |
| list_del(&m_entry->nft_flow_list); |
| |
| if (m_entry->next_pre_ct_entry) { |
| struct nfp_fl_ct_map_entry pre_ct_map_ent; |
| |
| pre_ct_map_ent.ct_entry = m_entry->next_pre_ct_entry; |
| pre_ct_map_ent.cookie = 0; |
| nfp_fl_ct_del_flow(&pre_ct_map_ent); |
| } |
| |
| kfree(m_entry); |
| } |
| |
| static void nfp_free_nft_merge_children(void *entry, bool is_nft_flow) |
| { |
| struct nfp_fl_nft_tc_merge *m_entry, *tmp; |
| |
| /* These post entries are parts of two lists, one is a list of nft_entries |
| * and the other is of from a list of tc_merge structures. Iterate |
| * through the relevant list and cleanup the entries. |
| */ |
| |
| if (is_nft_flow) { |
| /* Need to iterate through list of nft_flow entries */ |
| struct nfp_fl_ct_flow_entry *ct_entry = entry; |
| |
| list_for_each_entry_safe(m_entry, tmp, &ct_entry->children, |
| nft_flow_list) { |
| cleanup_nft_merge_entry(m_entry); |
| } |
| } else { |
| /* Need to iterate through list of tc_merged_flow entries */ |
| struct nfp_fl_ct_tc_merge *ct_entry = entry; |
| |
| list_for_each_entry_safe(m_entry, tmp, &ct_entry->children, |
| tc_merge_list) { |
| cleanup_nft_merge_entry(m_entry); |
| } |
| } |
| } |
| |
| static void nfp_del_tc_merge_entry(struct nfp_fl_ct_tc_merge *m_ent) |
| { |
| struct nfp_fl_ct_zone_entry *zt; |
| int err; |
| |
| zt = m_ent->zt; |
| err = rhashtable_remove_fast(&zt->tc_merge_tb, |
| &m_ent->hash_node, |
| nfp_tc_ct_merge_params); |
| if (err) |
| pr_warn("WARNING: could not remove merge_entry from hashtable\n"); |
| zt->tc_merge_count--; |
| list_del(&m_ent->post_ct_list); |
| list_del(&m_ent->pre_ct_list); |
| |
| if (!list_empty(&m_ent->children)) |
| nfp_free_nft_merge_children(m_ent, false); |
| kfree(m_ent); |
| } |
| |
| static void nfp_free_tc_merge_children(struct nfp_fl_ct_flow_entry *entry) |
| { |
| struct nfp_fl_ct_tc_merge *m_ent, *tmp; |
| |
| switch (entry->type) { |
| case CT_TYPE_PRE_CT: |
| list_for_each_entry_safe(m_ent, tmp, &entry->children, pre_ct_list) { |
| nfp_del_tc_merge_entry(m_ent); |
| } |
| break; |
| case CT_TYPE_POST_CT: |
| list_for_each_entry_safe(m_ent, tmp, &entry->children, post_ct_list) { |
| nfp_del_tc_merge_entry(m_ent); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void nfp_fl_ct_clean_flow_entry(struct nfp_fl_ct_flow_entry *entry) |
| { |
| list_del(&entry->list_node); |
| |
| if (!list_empty(&entry->children)) { |
| if (entry->type == CT_TYPE_NFT) |
| nfp_free_nft_merge_children(entry, true); |
| else |
| nfp_free_tc_merge_children(entry); |
| } |
| |
| if (entry->tun_offset != NFP_FL_CT_NO_TUN) |
| kfree(entry->rule->action.entries[entry->tun_offset].tunnel); |
| |
| if (entry->type == CT_TYPE_NFT) { |
| struct nf_flow_match *nft_match; |
| |
| nft_match = container_of(entry->rule->match.dissector, |
| struct nf_flow_match, dissector); |
| kfree(nft_match); |
| } |
| |
| kfree(entry->rule); |
| kfree(entry); |
| } |
| |
| static struct flow_action_entry *get_flow_act_ct(struct flow_rule *rule) |
| { |
| struct flow_action_entry *act; |
| int i; |
| |
| /* More than one ct action may be present in a flow rule, |
| * Return the first one that is not a CT clear action |
| */ |
| flow_action_for_each(i, act, &rule->action) { |
| if (act->id == FLOW_ACTION_CT && act->ct.action != TCA_CT_ACT_CLEAR) |
| return act; |
| } |
| |
| return NULL; |
| } |
| |
| static struct flow_action_entry *get_flow_act(struct flow_rule *rule, |
| enum flow_action_id act_id) |
| { |
| struct flow_action_entry *act = NULL; |
| int i; |
| |
| flow_action_for_each(i, act, &rule->action) { |
| if (act->id == act_id) |
| return act; |
| } |
| return NULL; |
| } |
| |
| static void |
| nfp_ct_merge_tc_entries(struct nfp_fl_ct_flow_entry *ct_entry1, |
| struct nfp_fl_ct_zone_entry *zt_src, |
| struct nfp_fl_ct_zone_entry *zt_dst) |
| { |
| struct nfp_fl_ct_flow_entry *ct_entry2, *ct_tmp; |
| struct list_head *ct_list; |
| |
| if (ct_entry1->type == CT_TYPE_PRE_CT) |
| ct_list = &zt_src->post_ct_list; |
| else if (ct_entry1->type == CT_TYPE_POST_CT) |
| ct_list = &zt_src->pre_ct_list; |
| else |
| return; |
| |
| list_for_each_entry_safe(ct_entry2, ct_tmp, ct_list, |
| list_node) { |
| nfp_ct_do_tc_merge(zt_dst, ct_entry2, ct_entry1); |
| } |
| } |
| |
| static void |
| nfp_ct_merge_nft_with_tc(struct nfp_fl_ct_flow_entry *nft_entry, |
| struct nfp_fl_ct_zone_entry *zt) |
| { |
| struct nfp_fl_ct_tc_merge *tc_merge_entry; |
| struct rhashtable_iter iter; |
| |
| rhashtable_walk_enter(&zt->tc_merge_tb, &iter); |
| rhashtable_walk_start(&iter); |
| while ((tc_merge_entry = rhashtable_walk_next(&iter)) != NULL) { |
| if (IS_ERR(tc_merge_entry)) |
| continue; |
| rhashtable_walk_stop(&iter); |
| nfp_ct_do_nft_merge(zt, nft_entry, tc_merge_entry); |
| rhashtable_walk_start(&iter); |
| } |
| rhashtable_walk_stop(&iter); |
| rhashtable_walk_exit(&iter); |
| } |
| |
| int nfp_fl_ct_handle_pre_ct(struct nfp_flower_priv *priv, |
| struct net_device *netdev, |
| struct flow_cls_offload *flow, |
| struct netlink_ext_ack *extack, |
| struct nfp_fl_nft_tc_merge *m_entry) |
| { |
| struct flow_action_entry *ct_act, *ct_goto; |
| struct nfp_fl_ct_flow_entry *ct_entry; |
| struct nfp_fl_ct_zone_entry *zt; |
| int err; |
| |
| ct_act = get_flow_act_ct(flow->rule); |
| if (!ct_act) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "unsupported offload: Conntrack action empty in conntrack offload"); |
| return -EOPNOTSUPP; |
| } |
| |
| ct_goto = get_flow_act(flow->rule, FLOW_ACTION_GOTO); |
| if (!ct_goto) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "unsupported offload: Conntrack requires ACTION_GOTO"); |
| return -EOPNOTSUPP; |
| } |
| |
| zt = get_nfp_zone_entry(priv, ct_act->ct.zone, false); |
| if (IS_ERR(zt)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "offload error: Could not create zone table entry"); |
| return PTR_ERR(zt); |
| } |
| |
| if (!zt->nft) { |
| zt->nft = ct_act->ct.flow_table; |
| err = nf_flow_table_offload_add_cb(zt->nft, nfp_fl_ct_handle_nft_flow, zt); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "offload error: Could not register nft_callback"); |
| return err; |
| } |
| } |
| |
| /* Add entry to pre_ct_list */ |
| ct_entry = nfp_fl_ct_add_flow(zt, netdev, flow, false, extack); |
| if (IS_ERR(ct_entry)) |
| return PTR_ERR(ct_entry); |
| ct_entry->type = CT_TYPE_PRE_CT; |
| ct_entry->chain_index = flow->common.chain_index; |
| ct_entry->goto_chain_index = ct_goto->chain_index; |
| |
| if (m_entry) { |
| struct nfp_fl_ct_flow_entry *pre_ct_entry; |
| int i; |
| |
| pre_ct_entry = m_entry->tc_m_parent->pre_ct_parent; |
| for (i = 0; i < pre_ct_entry->num_prev_m_entries; i++) |
| ct_entry->prev_m_entries[i] = pre_ct_entry->prev_m_entries[i]; |
| ct_entry->prev_m_entries[i++] = m_entry; |
| ct_entry->num_prev_m_entries = i; |
| |
| m_entry->next_pre_ct_entry = ct_entry; |
| } |
| |
| list_add(&ct_entry->list_node, &zt->pre_ct_list); |
| zt->pre_ct_count++; |
| |
| nfp_ct_merge_tc_entries(ct_entry, zt, zt); |
| |
| /* Need to check and merge with tables in the wc_zone as well */ |
| if (priv->ct_zone_wc) |
| nfp_ct_merge_tc_entries(ct_entry, priv->ct_zone_wc, zt); |
| |
| return 0; |
| } |
| |
| int nfp_fl_ct_handle_post_ct(struct nfp_flower_priv *priv, |
| struct net_device *netdev, |
| struct flow_cls_offload *flow, |
| struct netlink_ext_ack *extack) |
| { |
| struct flow_rule *rule = flow_cls_offload_flow_rule(flow); |
| struct nfp_fl_ct_flow_entry *ct_entry; |
| struct flow_action_entry *ct_goto; |
| struct nfp_fl_ct_zone_entry *zt; |
| struct flow_action_entry *act; |
| bool wildcarded = false; |
| struct flow_match_ct ct; |
| int i; |
| |
| flow_action_for_each(i, act, &rule->action) { |
| switch (act->id) { |
| case FLOW_ACTION_REDIRECT: |
| case FLOW_ACTION_REDIRECT_INGRESS: |
| case FLOW_ACTION_MIRRED: |
| case FLOW_ACTION_MIRRED_INGRESS: |
| if (act->dev->rtnl_link_ops && |
| !strcmp(act->dev->rtnl_link_ops->kind, "openvswitch")) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "unsupported offload: out port is openvswitch internal port"); |
| return -EOPNOTSUPP; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| flow_rule_match_ct(rule, &ct); |
| if (!ct.mask->ct_zone) { |
| wildcarded = true; |
| } else if (ct.mask->ct_zone != U16_MAX) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "unsupported offload: partially wildcarded ct_zone is not supported"); |
| return -EOPNOTSUPP; |
| } |
| |
| zt = get_nfp_zone_entry(priv, ct.key->ct_zone, wildcarded); |
| if (IS_ERR(zt)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "offload error: Could not create zone table entry"); |
| return PTR_ERR(zt); |
| } |
| |
| /* Add entry to post_ct_list */ |
| ct_entry = nfp_fl_ct_add_flow(zt, netdev, flow, false, extack); |
| if (IS_ERR(ct_entry)) |
| return PTR_ERR(ct_entry); |
| |
| ct_entry->type = CT_TYPE_POST_CT; |
| ct_entry->chain_index = flow->common.chain_index; |
| ct_goto = get_flow_act(flow->rule, FLOW_ACTION_GOTO); |
| ct_entry->goto_chain_index = ct_goto ? ct_goto->chain_index : 0; |
| list_add(&ct_entry->list_node, &zt->post_ct_list); |
| zt->post_ct_count++; |
| |
| if (wildcarded) { |
| /* Iterate through all zone tables if not empty, look for merges with |
| * pre_ct entries and merge them. |
| */ |
| struct rhashtable_iter iter; |
| struct nfp_fl_ct_zone_entry *zone_table; |
| |
| rhashtable_walk_enter(&priv->ct_zone_table, &iter); |
| rhashtable_walk_start(&iter); |
| while ((zone_table = rhashtable_walk_next(&iter)) != NULL) { |
| if (IS_ERR(zone_table)) |
| continue; |
| rhashtable_walk_stop(&iter); |
| nfp_ct_merge_tc_entries(ct_entry, zone_table, zone_table); |
| rhashtable_walk_start(&iter); |
| } |
| rhashtable_walk_stop(&iter); |
| rhashtable_walk_exit(&iter); |
| } else { |
| nfp_ct_merge_tc_entries(ct_entry, zt, zt); |
| } |
| |
| return 0; |
| } |
| |
| int nfp_fl_create_new_pre_ct(struct nfp_fl_nft_tc_merge *m_entry) |
| { |
| struct nfp_fl_ct_flow_entry *pre_ct_entry, *post_ct_entry; |
| struct flow_cls_offload new_pre_ct_flow; |
| int err; |
| |
| pre_ct_entry = m_entry->tc_m_parent->pre_ct_parent; |
| if (pre_ct_entry->num_prev_m_entries >= NFP_MAX_RECIRC_CT_ZONES - 1) |
| return -1; |
| |
| post_ct_entry = m_entry->tc_m_parent->post_ct_parent; |
| memset(&new_pre_ct_flow, 0, sizeof(struct flow_cls_offload)); |
| new_pre_ct_flow.rule = post_ct_entry->rule; |
| new_pre_ct_flow.common.chain_index = post_ct_entry->chain_index; |
| |
| err = nfp_fl_ct_handle_pre_ct(pre_ct_entry->zt->priv, |
| pre_ct_entry->netdev, |
| &new_pre_ct_flow, NULL, |
| m_entry); |
| return err; |
| } |
| |
| static void |
| nfp_fl_ct_sub_stats(struct nfp_fl_nft_tc_merge *nft_merge, |
| enum ct_entry_type type, u64 *m_pkts, |
| u64 *m_bytes, u64 *m_used) |
| { |
| struct nfp_flower_priv *priv = nft_merge->zt->priv; |
| struct nfp_fl_payload *nfp_flow; |
| u32 ctx_id; |
| |
| nfp_flow = nft_merge->flow_pay; |
| if (!nfp_flow) |
| return; |
| |
| ctx_id = be32_to_cpu(nfp_flow->meta.host_ctx_id); |
| *m_pkts += priv->stats[ctx_id].pkts; |
| *m_bytes += priv->stats[ctx_id].bytes; |
| *m_used = max_t(u64, *m_used, priv->stats[ctx_id].used); |
| |
| /* If request is for a sub_flow which is part of a tunnel merged |
| * flow then update stats from tunnel merged flows first. |
| */ |
| if (!list_empty(&nfp_flow->linked_flows)) |
| nfp_flower_update_merge_stats(priv->app, nfp_flow); |
| |
| if (type != CT_TYPE_NFT) { |
| /* Update nft cached stats */ |
| flow_stats_update(&nft_merge->nft_parent->stats, |
| priv->stats[ctx_id].bytes, |
| priv->stats[ctx_id].pkts, |
| 0, priv->stats[ctx_id].used, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| } else { |
| /* Update pre_ct cached stats */ |
| flow_stats_update(&nft_merge->tc_m_parent->pre_ct_parent->stats, |
| priv->stats[ctx_id].bytes, |
| priv->stats[ctx_id].pkts, |
| 0, priv->stats[ctx_id].used, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| /* Update post_ct cached stats */ |
| flow_stats_update(&nft_merge->tc_m_parent->post_ct_parent->stats, |
| priv->stats[ctx_id].bytes, |
| priv->stats[ctx_id].pkts, |
| 0, priv->stats[ctx_id].used, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| } |
| |
| /* Update previous pre_ct/post_ct/nft flow stats */ |
| if (nft_merge->tc_m_parent->pre_ct_parent->num_prev_m_entries > 0) { |
| struct nfp_fl_nft_tc_merge *tmp_nft_merge; |
| int i; |
| |
| for (i = 0; i < nft_merge->tc_m_parent->pre_ct_parent->num_prev_m_entries; i++) { |
| tmp_nft_merge = nft_merge->tc_m_parent->pre_ct_parent->prev_m_entries[i]; |
| flow_stats_update(&tmp_nft_merge->tc_m_parent->pre_ct_parent->stats, |
| priv->stats[ctx_id].bytes, |
| priv->stats[ctx_id].pkts, |
| 0, priv->stats[ctx_id].used, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| flow_stats_update(&tmp_nft_merge->tc_m_parent->post_ct_parent->stats, |
| priv->stats[ctx_id].bytes, |
| priv->stats[ctx_id].pkts, |
| 0, priv->stats[ctx_id].used, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| flow_stats_update(&tmp_nft_merge->nft_parent->stats, |
| priv->stats[ctx_id].bytes, |
| priv->stats[ctx_id].pkts, |
| 0, priv->stats[ctx_id].used, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| } |
| } |
| |
| /* Reset stats from the nfp */ |
| priv->stats[ctx_id].pkts = 0; |
| priv->stats[ctx_id].bytes = 0; |
| } |
| |
| int nfp_fl_ct_stats(struct flow_cls_offload *flow, |
| struct nfp_fl_ct_map_entry *ct_map_ent) |
| { |
| struct nfp_fl_ct_flow_entry *ct_entry = ct_map_ent->ct_entry; |
| struct nfp_fl_nft_tc_merge *nft_merge, *nft_m_tmp; |
| struct nfp_fl_ct_tc_merge *tc_merge, *tc_m_tmp; |
| |
| u64 pkts = 0, bytes = 0, used = 0; |
| u64 m_pkts, m_bytes, m_used; |
| |
| spin_lock_bh(&ct_entry->zt->priv->stats_lock); |
| |
| if (ct_entry->type == CT_TYPE_PRE_CT) { |
| /* Iterate tc_merge entries associated with this flow */ |
| list_for_each_entry_safe(tc_merge, tc_m_tmp, &ct_entry->children, |
| pre_ct_list) { |
| m_pkts = 0; |
| m_bytes = 0; |
| m_used = 0; |
| /* Iterate nft_merge entries associated with this tc_merge flow */ |
| list_for_each_entry_safe(nft_merge, nft_m_tmp, &tc_merge->children, |
| tc_merge_list) { |
| nfp_fl_ct_sub_stats(nft_merge, CT_TYPE_PRE_CT, |
| &m_pkts, &m_bytes, &m_used); |
| } |
| pkts += m_pkts; |
| bytes += m_bytes; |
| used = max_t(u64, used, m_used); |
| /* Update post_ct partner */ |
| flow_stats_update(&tc_merge->post_ct_parent->stats, |
| m_bytes, m_pkts, 0, m_used, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| } |
| } else if (ct_entry->type == CT_TYPE_POST_CT) { |
| /* Iterate tc_merge entries associated with this flow */ |
| list_for_each_entry_safe(tc_merge, tc_m_tmp, &ct_entry->children, |
| post_ct_list) { |
| m_pkts = 0; |
| m_bytes = 0; |
| m_used = 0; |
| /* Iterate nft_merge entries associated with this tc_merge flow */ |
| list_for_each_entry_safe(nft_merge, nft_m_tmp, &tc_merge->children, |
| tc_merge_list) { |
| nfp_fl_ct_sub_stats(nft_merge, CT_TYPE_POST_CT, |
| &m_pkts, &m_bytes, &m_used); |
| } |
| pkts += m_pkts; |
| bytes += m_bytes; |
| used = max_t(u64, used, m_used); |
| /* Update pre_ct partner */ |
| flow_stats_update(&tc_merge->pre_ct_parent->stats, |
| m_bytes, m_pkts, 0, m_used, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| } |
| } else { |
| /* Iterate nft_merge entries associated with this nft flow */ |
| list_for_each_entry_safe(nft_merge, nft_m_tmp, &ct_entry->children, |
| nft_flow_list) { |
| nfp_fl_ct_sub_stats(nft_merge, CT_TYPE_NFT, |
| &pkts, &bytes, &used); |
| } |
| } |
| |
| /* Add stats from this request to stats potentially cached by |
| * previous requests. |
| */ |
| flow_stats_update(&ct_entry->stats, bytes, pkts, 0, used, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| /* Finally update the flow stats from the original stats request */ |
| flow_stats_update(&flow->stats, ct_entry->stats.bytes, |
| ct_entry->stats.pkts, 0, |
| ct_entry->stats.lastused, |
| FLOW_ACTION_HW_STATS_DELAYED); |
| /* Stats has been synced to original flow, can now clear |
| * the cache. |
| */ |
| ct_entry->stats.pkts = 0; |
| ct_entry->stats.bytes = 0; |
| spin_unlock_bh(&ct_entry->zt->priv->stats_lock); |
| |
| return 0; |
| } |
| |
| static bool |
| nfp_fl_ct_offload_nft_supported(struct flow_cls_offload *flow) |
| { |
| struct flow_rule *flow_rule = flow->rule; |
| struct flow_action *flow_action = |
| &flow_rule->action; |
| struct flow_action_entry *act; |
| int i; |
| |
| flow_action_for_each(i, act, flow_action) { |
| if (act->id == FLOW_ACTION_CT_METADATA) { |
| enum ip_conntrack_info ctinfo = |
| act->ct_metadata.cookie & NFCT_INFOMASK; |
| |
| return ctinfo != IP_CT_NEW; |
| } |
| } |
| |
| return false; |
| } |
| |
| static int |
| nfp_fl_ct_offload_nft_flow(struct nfp_fl_ct_zone_entry *zt, struct flow_cls_offload *flow) |
| { |
| struct nfp_fl_ct_map_entry *ct_map_ent; |
| struct nfp_fl_ct_flow_entry *ct_entry; |
| struct netlink_ext_ack *extack = NULL; |
| |
| extack = flow->common.extack; |
| switch (flow->command) { |
| case FLOW_CLS_REPLACE: |
| if (!nfp_fl_ct_offload_nft_supported(flow)) |
| return -EOPNOTSUPP; |
| |
| /* Netfilter can request offload multiple times for the same |
| * flow - protect against adding duplicates. |
| */ |
| ct_map_ent = rhashtable_lookup_fast(&zt->priv->ct_map_table, &flow->cookie, |
| nfp_ct_map_params); |
| if (!ct_map_ent) { |
| ct_entry = nfp_fl_ct_add_flow(zt, NULL, flow, true, extack); |
| if (IS_ERR(ct_entry)) |
| return PTR_ERR(ct_entry); |
| ct_entry->type = CT_TYPE_NFT; |
| list_add(&ct_entry->list_node, &zt->nft_flows_list); |
| zt->nft_flows_count++; |
| nfp_ct_merge_nft_with_tc(ct_entry, zt); |
| } |
| return 0; |
| case FLOW_CLS_DESTROY: |
| ct_map_ent = rhashtable_lookup_fast(&zt->priv->ct_map_table, &flow->cookie, |
| nfp_ct_map_params); |
| return nfp_fl_ct_del_flow(ct_map_ent); |
| case FLOW_CLS_STATS: |
| ct_map_ent = rhashtable_lookup_fast(&zt->priv->ct_map_table, &flow->cookie, |
| nfp_ct_map_params); |
| if (ct_map_ent) |
| return nfp_fl_ct_stats(flow, ct_map_ent); |
| break; |
| default: |
| break; |
| } |
| return -EINVAL; |
| } |
| |
| int nfp_fl_ct_handle_nft_flow(enum tc_setup_type type, void *type_data, void *cb_priv) |
| { |
| struct flow_cls_offload *flow = type_data; |
| struct nfp_fl_ct_zone_entry *zt = cb_priv; |
| int err = -EOPNOTSUPP; |
| |
| switch (type) { |
| case TC_SETUP_CLSFLOWER: |
| while (!mutex_trylock(&zt->priv->nfp_fl_lock)) { |
| if (!zt->nft) /* avoid deadlock */ |
| return err; |
| msleep(20); |
| } |
| err = nfp_fl_ct_offload_nft_flow(zt, flow); |
| mutex_unlock(&zt->priv->nfp_fl_lock); |
| break; |
| default: |
| return -EOPNOTSUPP; |
| } |
| return err; |
| } |
| |
| static void |
| nfp_fl_ct_clean_nft_entries(struct nfp_fl_ct_zone_entry *zt) |
| { |
| struct nfp_fl_ct_flow_entry *nft_entry, *ct_tmp; |
| struct nfp_fl_ct_map_entry *ct_map_ent; |
| |
| list_for_each_entry_safe(nft_entry, ct_tmp, &zt->nft_flows_list, |
| list_node) { |
| ct_map_ent = rhashtable_lookup_fast(&zt->priv->ct_map_table, |
| &nft_entry->cookie, |
| nfp_ct_map_params); |
| nfp_fl_ct_del_flow(ct_map_ent); |
| } |
| } |
| |
| int nfp_fl_ct_del_flow(struct nfp_fl_ct_map_entry *ct_map_ent) |
| { |
| struct nfp_fl_ct_flow_entry *ct_entry; |
| struct nfp_fl_ct_zone_entry *zt; |
| struct rhashtable *m_table; |
| struct nf_flowtable *nft; |
| |
| if (!ct_map_ent) |
| return -ENOENT; |
| |
| zt = ct_map_ent->ct_entry->zt; |
| ct_entry = ct_map_ent->ct_entry; |
| m_table = &zt->priv->ct_map_table; |
| |
| switch (ct_entry->type) { |
| case CT_TYPE_PRE_CT: |
| zt->pre_ct_count--; |
| if (ct_map_ent->cookie > 0) |
| rhashtable_remove_fast(m_table, &ct_map_ent->hash_node, |
| nfp_ct_map_params); |
| nfp_fl_ct_clean_flow_entry(ct_entry); |
| if (ct_map_ent->cookie > 0) |
| kfree(ct_map_ent); |
| |
| if (!zt->pre_ct_count && zt->nft) { |
| nft = zt->nft; |
| zt->nft = NULL; /* avoid deadlock */ |
| nf_flow_table_offload_del_cb(nft, |
| nfp_fl_ct_handle_nft_flow, |
| zt); |
| nfp_fl_ct_clean_nft_entries(zt); |
| } |
| break; |
| case CT_TYPE_POST_CT: |
| zt->post_ct_count--; |
| rhashtable_remove_fast(m_table, &ct_map_ent->hash_node, |
| nfp_ct_map_params); |
| nfp_fl_ct_clean_flow_entry(ct_entry); |
| kfree(ct_map_ent); |
| break; |
| case CT_TYPE_NFT: |
| zt->nft_flows_count--; |
| rhashtable_remove_fast(m_table, &ct_map_ent->hash_node, |
| nfp_ct_map_params); |
| nfp_fl_ct_clean_flow_entry(ct_map_ent->ct_entry); |
| kfree(ct_map_ent); |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |