/** * 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.
*/ staticvoid *get_hashentry(struct rhashtable *ht, void *key, conststruct 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);
if (dissector->used_keys & BIT_ULL(FLOW_DISSECTOR_KEY_CT)) {
flow_rule_match_ct(rule, &ct); if (ct.key->ct_state) returnfalse;
}
if (flow->common.chain_index) returnfalse;
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)) returntrue; else returnfalse;
}
}
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) returntrue;
} 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;
}
returnfalse;
}
} /* 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) returntrue;
}
returnfalse;
}
/** * 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.
*/ staticvoid *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;
/* 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) staticvoid *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;
/* 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.
*/ staticbool 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) returnfalse;
returntrue;
}
/* 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.
*/ staticint nfp_ct_merge_check(struct nfp_fl_ct_flow_entry *entry1, struct nfp_fl_ct_flow_entry *entry2)
{ unsignedlonglong 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;
/* 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;
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;
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;
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
*/ staticint 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;
staticint 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;
}
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;
}
staticint
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;
/* get the csum flag according the ip proto and mangle action. */ staticvoid 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; elseif (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;
}
}
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;
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;
/* 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);
/* 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;
/* 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]);
}
}
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);
}
}
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 = ((unsignedlong)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;
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;
/* 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;
/* 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++;
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;
}
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;
}
staticvoid 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;
/* 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;
/* 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;
staticvoid 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;
}
/* 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;
staticvoid 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);
}
/* 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;
}
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;
}
}
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;
} elseif (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);
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;
/* 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);
/* 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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.