On 03/12/2024 18:24, Eelco Chaudron wrote:
> When flows are added through TC, only the match and actions are
> verified to determine if they can be handled by TC. If they can,
> the TC flow is installed.
> 
> However, when the flow is a continuation of a previously recirculated flow,
> it can happen that the flow performing the recirculation is installed
> in the kernel. This may occur, for example, if it includes an action that
> cannot be handled by TC.
> 
> If the kernel module has the first flow but not the second one (missing
> because it is programmed in TC), the flow is sent to userspace via an
> upcall.
> 
> This patch tracks which recirculation goto actions are handled by TC.
> A matching TC rule is installed only if the corresponding recirculation
> ID is confirmed to be handled by TC.
> 
> Signed-off-by: Eelco Chaudron <[email protected]>
> ---
>  lib/netdev-offload-tc.c          | 51 ++++++++++++++++++++++++++++++--
>  tests/system-offloads-traffic.at | 45 ++++++++++++++++++++++++++++
>  2 files changed, 94 insertions(+), 2 deletions(-)
> 
> diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
> index 74e60d0f7..eb7c0886e 100644
> --- a/lib/netdev-offload-tc.c
> +++ b/lib/netdev-offload-tc.c
> @@ -19,6 +19,7 @@
>  #include <errno.h>
>  #include <linux/if_ether.h>
>  
> +#include "ccmap.h"
>  #include "dpif.h"
>  #include "hash.h"
>  #include "id-pool.h"
> @@ -78,6 +79,10 @@ struct policer_node {
>      uint32_t police_idx;
>  };
>  
> +/* ccmap and protective mutex for counting recirculation id (chain) usage. */
> +static struct ovs_mutex used_chains_mutex = OVS_MUTEX_INITIALIZER;
> +static struct ccmap used_chains OVS_GUARDED;
> +
>  /* Protects below meter police ids pool. */
>  static struct ovs_mutex meter_police_ids_mutex = OVS_MUTEX_INITIALIZER;
>  static struct id_pool *meter_police_ids 
> OVS_GUARDED_BY(meter_police_ids_mutex);
> @@ -204,6 +209,10 @@ static struct ovs_mutex ufid_lock = 
> OVS_MUTEX_INITIALIZER;
>   * @adjust_stats: When flow gets updated with new actions, we need to adjust
>   *                the reported stats to include previous values as the 
> hardware
>   *                rule is removed and re-added. This stats copy is used for 
> it.
> + * @chain_goto: If a TC jump action exists for the flow, the target chain it
> + *              jumps to is stored here.  Only a single goto action is 
> stored,
> + *              as TC supports only one goto action per flow (there is no
> + *              return mechanism).
>   */
>  struct ufid_tc_data {
>      struct hmap_node ufid_to_tc_node;
> @@ -212,6 +221,7 @@ struct ufid_tc_data {
>      struct tcf_id id;
>      struct netdev *netdev;
>      struct dpif_flow_stats adjust_stats;
> +    uint32_t chain_goto;
>  };
>  
>  static void
> @@ -233,6 +243,13 @@ del_ufid_tc_mapping_unlocked(const ovs_u128 *ufid)
>      hmap_remove(&ufid_to_tc, &data->ufid_to_tc_node);
>      hmap_remove(&tc_to_ufid, &data->tc_to_ufid_node);
>      netdev_close(data->netdev);
> +
> +    if (data->chain_goto) {
> +        ovs_mutex_lock(&used_chains_mutex);
> +        ccmap_dec(&used_chains, data->chain_goto);
> +        ovs_mutex_unlock(&used_chains_mutex);
> +    }
> +
>      free(data);
>  }
>  
> @@ -288,7 +305,8 @@ del_filter_and_ufid_mapping(struct tcf_id *id, const 
> ovs_u128 *ufid,
>  /* Add ufid entry to ufid_to_tc hashmap. */
>  static void
>  add_ufid_tc_mapping(struct netdev *netdev, const ovs_u128 *ufid,
> -                    struct tcf_id *id, struct dpif_flow_stats *stats)
> +                    struct tcf_id *id, struct dpif_flow_stats *stats,
> +                    uint32_t chain_goto)
>  {
>      struct ufid_tc_data *new_data = xzalloc(sizeof *new_data);
>      size_t ufid_hash = hash_bytes(ufid, sizeof *ufid, 0);
> @@ -300,6 +318,7 @@ add_ufid_tc_mapping(struct netdev *netdev, const ovs_u128 
> *ufid,
>      new_data->ufid = *ufid;
>      new_data->id = *id;
>      new_data->netdev = netdev_ref(netdev);
> +    new_data->chain_goto = chain_goto;
>      if (stats) {
>          new_data->adjust_stats = *stats;
>      }
> @@ -2261,6 +2280,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct match 
> *match,
>      struct flow_tnl *tnl_mask = &mask->tunnel;
>      struct dpif_flow_stats adjust_stats;
>      bool recirc_act = false;
> +    uint32_t chain_goto = 0;
>      uint32_t block_id = 0;
>      struct tcf_id id;
>      uint32_t chain;
> @@ -2280,6 +2300,17 @@ netdev_tc_flow_put(struct netdev *netdev, struct match 
> *match,
>      chain = key->recirc_id;
>      mask->recirc_id = 0;
>  
> +    if (chain) {
> +        /* If we match on a recirculation ID, we must ensure the previous
> +         * flow is also in the TC datapath; otherwise, the entry is useless,
> +         * as the related packets will be handled by upcalls. */
> +        if (!ccmap_find(&used_chains, chain)) {
> +            VLOG_DBG_RL(&rl, "match for chain %u failed due to non-existing "
> +                        "goto chain action", chain);
> +            return EOPNOTSUPP;
> +        }
> +    }
> +
>      if (flow_tnl_dst_is_set(&key->tunnel) ||
>          flow_tnl_src_is_set(&key->tunnel)) {
>          VLOG_DBG_RL(&rl,
> @@ -2594,6 +2625,20 @@ netdev_tc_flow_put(struct netdev *netdev, struct match 
> *match,
>          return EOPNOTSUPP;
>      }
>  
> +    if (recirc_act) {
> +        struct tc_action *action = flower.actions;
> +
> +        for (int i = 0; i < flower.action_count; i++, action++) {
> +            if (action->type == TC_ACT_GOTO && action->chain) {
> +                chain_goto = action->chain;
> +                ovs_mutex_lock(&used_chains_mutex);
> +                ccmap_inc(&used_chains, chain_goto);
> +                ovs_mutex_unlock(&used_chains_mutex);
> +                break;
> +            }
> +        }
> +    }
> +
>      memset(&adjust_stats, 0, sizeof adjust_stats);
>      if (get_ufid_tc_mapping(ufid, &id) == 0) {
>          VLOG_DBG_RL(&rl, "updating old handle: %d prio: %d",
> @@ -2619,7 +2664,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct match 
> *match,
>              memset(stats, 0, sizeof *stats);
>              netdev_tc_adjust_stats(stats, &adjust_stats);
>          }
> -        add_ufid_tc_mapping(netdev, ufid, &id, &adjust_stats);
> +        add_ufid_tc_mapping(netdev, ufid, &id, &adjust_stats, chain_goto);
>      }
>  
>      return err;
> @@ -3096,6 +3141,8 @@ netdev_tc_init_flow_api(struct netdev *netdev)
>      tc_add_del_qdisc(ifindex, false, 0, hook);
>  
>      if (ovsthread_once_start(&once)) {
> +        ccmap_init(&used_chains);
> +
>          probe_tc_block_support(ifindex);
>          /* Need to re-fetch block id as it depends on feature availability. 
> */
>          block_id = get_block_id_from_netdev(netdev);
> diff --git a/tests/system-offloads-traffic.at 
> b/tests/system-offloads-traffic.at
> index 78c6f5d7e..f890ced68 100644
> --- a/tests/system-offloads-traffic.at
> +++ b/tests/system-offloads-traffic.at
> @@ -1015,5 +1015,50 @@ AT_CHECK(
>    [grep -q -F "set(tunnel(dst=172.31.1.1,ttl=64,tp_dst=4789,flags(csum)))" \
>     stdout])
>  
> +OVS_TRAFFIC_VSWITCHD_STOP
> +AT_CLEANUP
> +
> +AT_SETUP([offloads - split kernel vs offload datapath rules])
> +OVS_TRAFFIC_VSWITCHD_START([], [], [-- set Open_vSwitch . 
> other_config:hw-offload=true])
> +
> +ADD_NAMESPACES(at_ns0, at_ns1)
> +
> +ADD_VETH(p1, at_ns0, br0, "10.0.0.1/24")
> +ADD_VETH(p2, at_ns1, br0, "10.0.0.2/24")
> +
> +AT_CHECK([ovs-vsctl -- set interface ovs-p1 ofport_request=1 \
> +                    -- set interface ovs-p2 ofport_request=2])
> +
> +AT_DATA([groups.txt], [dnl
> +group_id=1,type=select,selection_method=dp_hash,bucket=bucket_id:0,weight:100,actions=ct(commit,table=2)
> +])
> +AT_DATA([flows.txt], [dnl
> +table=0,arp,actions=NORMAL
> +table=0,priority=100,cookie=0x12345678,in_port=ovs-p1,ip,nw_dst=10.0.0.2,actions=resubmit(,1)
> +table=0,priority=100,cookie=0xabcedf,in_port=ovs-p2,ip,nw_dst=10.0.0.1,actions=ct(table=3)
> +table=1,priority=200,ip,actions=group:1
> +table=2,ip,actions=ovs-p2
> +table=3,ip,actions=ovs-p1
> +])
> +AT_CHECK([ovs-ofctl add-groups br0 groups.txt])
> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
> +
> +NS_CHECK_EXEC([at_ns0], [ping -q -c 10 -i 0.1 -W 2 10.0.0.2 | FORMAT_PING], 
> [0], [dnl
> +10 packets transmitted, 10 received, 0% packet loss, time 0ms
> +])
> +
> +AT_CHECK([ovs-appctl revalidator/wait])
> +
> +dnl In this test we should not have the first recirculation(s) in the kernel
> +dnl datapath, and the final flow in tc.  They should all be in the kernel
> +dnl datapath, as the dp_hash() action is currently not supported by TC.
> +dnl The command below ensures they are all handled in the kernel datapath.
> +AT_CHECK([ovs-appctl dpctl/dump-flows --names type=ovs 
> filter='in_port(ovs-p1),ipv4' | \
> +          strip_recirc | strip_dp_hash | DUMP_CLEAN_SORTED], [0], [dnl
> +recirc_id(<recirc>),in_port(ovs-p1),eth(),eth_type(0x0800),ipv4(dst=10.0.0.2,frag=no),
>  packets:9, bytes:882, used:0.001s, actions:hash(l4(0)),recirc(<recirc>)
> +recirc_id(<recirc>),in_port(ovs-p1),eth(),eth_type(0x0800),ipv4(frag=no), 
> packets:9, bytes:882, used:0.001s, actions:ct(commit),recirc(<recirc>)
> +recirc_id(<recirc>),in_port(ovs-p1),eth(),eth_type(0x0800),ipv4(frag=no), 
> packets:9, bytes:882, used:0.001s, actions:ovs-p2
> +])
> +
>  OVS_TRAFFIC_VSWITCHD_STOP
>  AT_CLEANUP
> \ No newline at end of file

Acked-by: Roi Dayan <[email protected]>

nice.
Thanks,
Roi



_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to