Create a unique group ID to map the sFlow info when offloading sample
action to TC. When showing the offloaded datapath flows, translate the
group ID from TC sample action to sFlow info using the mapping.

Signed-off-by: Chris Mi <c...@nvidia.com>
Reviewed-by: Roi Dayan <r...@nvidia.com>
---
 lib/netdev-offload-tc.c | 267 +++++++++++++++++++++++++++++++++++++---
 lib/netdev-offload.h    |   1 +
 lib/tc.c                |  62 +++++++++-
 lib/tc.h                |  11 +-
 4 files changed, 319 insertions(+), 22 deletions(-)

diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index ce57f37c8..be5f4d2be 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -92,13 +92,23 @@ static struct hmap police_idx_to_meter_id 
OVS_GUARDED_BY(meter_mutex)
 static int meter_id_lookup(uint32_t meter_id, uint32_t *police_idx);
 static int police_idx_lookup(uint32_t police_idx, uint32_t *meter_id);
 
+/* To avoid passing too many parameters when offloading sample action,
+ * aggregate them here.
+ */
+struct sample_param {
+    const struct flow_tnl *tnl; /* Tunnel info. */
+    const ovs_u128 *ufid;       /* Ufid. */
+    uint32_t gid;               /* Generated gid. */
+};
+
 static int netdev_tc_parse_nl_actions(struct netdev *netdev,
                                       struct tc_flower *flower,
                                       struct offload_info *info,
                                       const struct nlattr *actions,
                                       size_t actions_len,
                                       bool *recirc_act, bool more_actions,
-                                      struct tc_action **need_jump_update);
+                                      struct tc_action **need_jump_update,
+                                      struct sample_param *param);
 
 static void parse_tc_flower_to_stats(struct tc_flower *flower,
                                      struct dpif_flow_stats *stats);
@@ -109,6 +119,12 @@ static int get_ufid_adjust_stats(const ovs_u128 *ufid,
 static struct nl_sock *psample_sock;
 static int psample_family;
 
+static bool
+psample_supported(void)
+{
+    return psample_sock != NULL;
+}
+
 /* When offloading sample action to TC, userspace creates a unique ID
  * to map sFlow action and tunnel info and passes this ID to kernel
  * instead of the sFlow info. Psample will send this ID and sampled
@@ -997,6 +1013,19 @@ parse_tc_flower_to_actions__(struct tc_flower *flower, 
struct ofpbuf *buf,
         action = &flower->actions[i];
 
         switch (action->type) {
+        case TC_ACT_SAMPLE: {
+            const struct sgid_node *node;
+
+            node = sgid_find(action->sample.group_id);
+            if (!node) {
+                VLOG_WARN("Can't find sample group ID data for ID: %u",
+                          action->sample.group_id);
+                return ENOENT;
+            }
+            nl_msg_put(buf, node->sflow.action,
+                       node->sflow.action->nla_len);
+        }
+        break;
         case TC_ACT_VLAN_POP: {
             nl_msg_put_flag(buf, OVS_ACTION_ATTR_POP_VLAN);
         }
@@ -2102,6 +2131,174 @@ parse_match_ct_state_to_flower(struct tc_flower 
*flower, struct match *match)
     }
 }
 
+static int
+parse_userspace_attributes(const struct nlattr *actions,
+                           struct offload_sflow *sflow)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+    const struct nlattr *nla;
+    unsigned int left;
+
+    NL_NESTED_FOR_EACH_UNSAFE (nla, left, actions) {
+        if (nl_attr_type(nla) == OVS_USERSPACE_ATTR_USERDATA) {
+            struct user_action_cookie *cookie;
+
+            cookie = (struct user_action_cookie *) nl_attr_get(nla);
+            if (cookie->type == USER_ACTION_COOKIE_SFLOW) {
+                sflow->userdata = (struct nlattr *) nla;
+                return 0;
+            }
+        }
+    }
+
+    VLOG_DBG_RL(&rl, "Can't offload userspace action other than sFlow");
+    return EOPNOTSUPP;
+}
+
+static int
+parse_sample_actions_attribute(const struct nlattr *actions,
+                               struct offload_sflow *sflow)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+    const struct nlattr *nla;
+    unsigned int left;
+    int err = EINVAL;
+
+    NL_NESTED_FOR_EACH_UNSAFE (nla, left, actions) {
+        if (nl_attr_type(nla) == OVS_ACTION_ATTR_USERSPACE) {
+            err = parse_userspace_attributes(nla, sflow);
+        } else {
+            /* We can't offload other nested actions. */
+            VLOG_DBG_RL(&rl, "Can only offload OVS_ACTION_ATTR_USERSPACE"
+                             " attribute");
+            return EINVAL;
+        }
+    }
+
+    if (err) {
+        VLOG_ERR_RL(&error_rl, "No OVS_ACTION_ATTR_USERSPACE attribute");
+    }
+    return err;
+}
+
+static int
+offload_sflow_set(struct offload_sflow *sflow, const struct nlattr *actions,
+                  size_t actions_len, bool tunnel, struct sample_param *param)
+{
+    if (param->gid) {
+        VLOG_ERR_RL(&error_rl,
+                    "Only a single TC_SAMPLE action per flow is supported");
+        return EOPNOTSUPP;
+    }
+
+    memset(sflow, 0, sizeof *sflow);
+    sflow->ufid = *(param->ufid);
+    if (tunnel) {
+        sflow->tunnel = CONST_CAST(struct flow_tnl *, param->tnl);
+    } else {
+        sflow->actions = xmalloc(actions_len + NLA_HDRLEN);
+        nullable_memcpy((char *) sflow->actions + NLA_HDRLEN, actions,
+                         actions_len);
+        sflow->actions->nla_len = actions_len + NLA_HDRLEN;
+    }
+
+    return 0;
+}
+
+static int
+parse_sample_action(struct tc_flower *flower, struct tc_action *tc_action,
+                    const struct nlattr *actions, size_t actions_len,
+                    const struct nlattr *sample_action,
+                    struct sample_param *param)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+    struct offload_sflow sflow;
+    const struct nlattr *nla;
+    unsigned int left;
+    int err;
+
+    err = offload_sflow_set(&sflow, actions, actions_len, flower->tunnel,
+                            param);
+    if (err) {
+        return err;
+    }
+    sflow.action = (struct nlattr *) sample_action;
+
+    NL_NESTED_FOR_EACH_UNSAFE (nla, left, sample_action) {
+        if (nl_attr_type(nla) == OVS_SAMPLE_ATTR_ACTIONS) {
+            err = parse_sample_actions_attribute(nla, &sflow);
+        } else if (nl_attr_type(nla) == OVS_SAMPLE_ATTR_PROBABILITY) {
+            tc_action->type = TC_ACT_SAMPLE;
+            tc_action->sample.rate = UINT32_MAX / nl_attr_get_u32(nla);
+        } else {
+            err = EINVAL;
+            goto out;
+        }
+    }
+
+    if (!tc_action->sample.rate || err) {
+        err = EINVAL;
+        goto out;
+    }
+
+    param->gid = sgid_alloc_ctx(&sflow);
+    if (!param->gid) {
+        VLOG_DBG_RL(&rl, "Failed allocating group id for sample action");
+        err = ENOENT;
+        goto out;
+    }
+    tc_action->sample.group_id = param->gid;
+    flower->action_count++;
+
+out:
+    if (sflow.actions) {
+        free(sflow.actions);
+    }
+    return err;
+}
+
+static int
+parse_userspace_action(struct tc_flower *flower, struct tc_action *tc_action,
+                       const struct nlattr *actions, size_t actions_len,
+                       const struct nlattr *userspace_action,
+                       struct sample_param *param)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+    struct offload_sflow sflow;
+    int err;
+
+    err = offload_sflow_set(&sflow, actions, actions_len, flower->tunnel,
+                            param);
+    if (err) {
+        return err;
+    }
+
+    /* If sampling rate is 1, there is only a sFlow cookie inside of a
+     * userspace action, but no sample attribute. That means we can
+     * only offload userspace actions for sFlow.
+     */
+    err = parse_userspace_attributes(userspace_action, &sflow);
+    if (err) {
+        goto out;
+    }
+    sflow.action = (struct nlattr *) userspace_action;
+    param->gid = sgid_alloc_ctx(&sflow);
+    if (!param->gid) {
+        VLOG_DBG_RL(&rl, "Failed allocating group id for sample action");
+        err = ENOENT;
+        goto out;
+    }
+    tc_action->type = TC_ACT_SAMPLE;
+    tc_action->sample.group_id = param->gid;
+    tc_action->sample.rate = 1;
+    flower->action_count++;
+
+out:
+    if (sflow.actions) {
+        free(sflow.actions);
+    }
+    return err;
+}
 
 static int
 parse_check_pkt_len_action(struct netdev *netdev, struct tc_flower *flower,
@@ -2149,7 +2346,7 @@ parse_check_pkt_len_action(struct netdev *netdev, struct 
tc_flower *flower,
                                      nl_attr_get(nl_actions),
                                      nl_attr_get_size(nl_actions),
                                      recirc_act, !last_action,
-                                     &ge_jump_update);
+                                     &ge_jump_update, NULL);
     if (err) {
         return err;
     }
@@ -2165,7 +2362,7 @@ parse_check_pkt_len_action(struct netdev *netdev, struct 
tc_flower *flower,
                                      nl_attr_get(nl_actions),
                                      nl_attr_get_size(nl_actions),
                                      recirc_act, !last_action,
-                                     &le_jump_update);
+                                     &le_jump_update, NULL);
 
     if (gt_offset == flower->action_count && last_action) {
         /* No le actions where added, fix gt offset. */
@@ -2216,7 +2413,8 @@ netdev_tc_parse_nl_actions(struct netdev *netdev, struct 
tc_flower *flower,
                            struct offload_info *info,
                            const struct nlattr *actions, size_t actions_len,
                            bool *recirc_act, bool more_actions,
-                           struct tc_action **need_jump_update)
+                           struct tc_action **need_jump_update,
+                           struct sample_param *param)
 {
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
     const struct nlattr *nla;
@@ -2353,10 +2551,27 @@ netdev_tc_parse_nl_actions(struct netdev *netdev, 
struct tc_flower *flower,
                 return err;
             }
         } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_SAMPLE) {
-            struct offload_sflow sflow;
-
-            memset(&sflow, 0, sizeof sflow);
-            sgid_alloc_ctx(&sflow);
+            if (!psample_supported()) {
+                VLOG_DBG_RL(&rl, "Unsupported put action type: %d, psample is "
+                            "not initialized successfully", nl_attr_type(nla));
+                return EOPNOTSUPP;
+            }
+            err = parse_sample_action(flower, action, actions, actions_len,
+                                      nla, param);
+            if (err) {
+                return err;
+            }
+        } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_USERSPACE) {
+            if (!psample_supported()) {
+                VLOG_DBG_RL(&rl, "Unsupported put action type: %d, psample is "
+                            "not initialized successfully", nl_attr_type(nla));
+                return EOPNOTSUPP;
+            }
+            err = parse_userspace_action(flower, action, actions, actions_len,
+                                         nla, param);
+            if (err) {
+                return err;
+            }
         } else {
             VLOG_DBG_RL(&rl, "unsupported put action type: %d",
                         nl_attr_type(nla));
@@ -2380,6 +2595,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct match 
*match,
     const struct flow_tnl *tnl = &match->flow.tunnel;
     struct flow_tnl *tnl_mask = &mask->tunnel;
     struct dpif_flow_stats adjust_stats;
+    struct sample_param param;
     bool recirc_act = false;
     uint32_t block_id = 0;
     struct tcf_id id;
@@ -2660,17 +2876,21 @@ netdev_tc_flow_put(struct netdev *netdev, struct match 
*match,
         return err;
     }
 
+    param.tnl = tnl;
+    param.ufid = ufid;
+    param.gid = 0;
     /* Parse all (nested) actions. */
     err = netdev_tc_parse_nl_actions(netdev, &flower, info,
                                      actions, actions_len, &recirc_act,
-                                     false, NULL);
+                                     false, NULL, &param);
     if (err) {
-        return err;
+        goto out;
     }
 
     if ((chain || recirc_act) && !info->recirc_id_shared_with_tc) {
         VLOG_DBG_RL(&rl, "flow_put: recirc_id sharing not supported");
-        return EOPNOTSUPP;
+        err = EOPNOTSUPP;
+        goto out;
     }
 
     memset(&adjust_stats, 0, sizeof adjust_stats);
@@ -2684,21 +2904,30 @@ netdev_tc_flow_put(struct netdev *netdev, struct match 
*match,
     prio = get_prio_for_tc_flower(&flower);
     if (prio == 0) {
         VLOG_ERR_RL(&rl, "couldn't get tc prio: %s", ovs_strerror(ENOSPC));
-        return ENOSPC;
+        err = ENOSPC;
+        goto out;
     }
 
     flower.act_cookie.data = ufid;
     flower.act_cookie.len = sizeof *ufid;
 
     block_id = get_block_id_from_netdev(netdev);
-    id = tc_make_tcf_id_chain(ifindex, block_id, chain, prio, hook);
+    id = tc_make_tcf_id_all(ifindex, block_id, chain, prio, hook, param.gid);
     err = tc_replace_flower(&id, &flower);
-    if (!err) {
-        if (stats) {
-            memset(stats, 0, sizeof *stats);
-            netdev_tc_adjust_stats(stats, &adjust_stats);
-        }
-        add_ufid_tc_mapping(netdev, ufid, &id, &adjust_stats);
+    if (err) {
+        goto out;
+    }
+
+    if (stats) {
+        memset(stats, 0, sizeof *stats);
+        netdev_tc_adjust_stats(stats, &adjust_stats);
+    }
+    add_ufid_tc_mapping(netdev, ufid, &id, &adjust_stats);
+    return 0;
+
+out:
+    if (param.gid) {
+        sgid_free(param.gid);
     }
 
     return err;
diff --git a/lib/netdev-offload.h b/lib/netdev-offload.h
index 09cdb6db2..1bf648c68 100644
--- a/lib/netdev-offload.h
+++ b/lib/netdev-offload.h
@@ -81,6 +81,7 @@ struct offload_info {
     bool tc_modify_flow_deleted; /* Indicate the tc modify flow put success
                                   * to delete the original flow. */
     odp_port_t orig_in_port; /* Originating in_port for tnl flows. */
+
 };
 
 DECLARE_EXTERN_PER_THREAD_DATA(unsigned int, netdev_offload_thread_id);
diff --git a/lib/tc.c b/lib/tc.c
index 4c07e2216..4bbecb26a 100644
--- a/lib/tc.c
+++ b/lib/tc.c
@@ -23,14 +23,15 @@
 #include <linux/if_packet.h>
 #include <linux/rtnetlink.h>
 #include <linux/tc_act/tc_csum.h>
+#include <linux/tc_act/tc_ct.h>
 #include <linux/tc_act/tc_gact.h>
 #include <linux/tc_act/tc_mirred.h>
 #include <linux/tc_act/tc_mpls.h>
 #include <linux/tc_act/tc_pedit.h>
+#include <linux/tc_act/tc_sample.h>
 #include <linux/tc_act/tc_skbedit.h>
 #include <linux/tc_act/tc_tunnel_key.h>
 #include <linux/tc_act/tc_vlan.h>
-#include <linux/tc_act/tc_ct.h>
 #include <linux/gen_stats.h>
 #include <net/if.h>
 #include <unistd.h>
@@ -1484,6 +1485,38 @@ nl_parse_act_police(const struct nlattr *options, struct 
tc_flower *flower)
     return 0;
 }
 
+static const struct nl_policy sample_policy[] = {
+    [TCA_SAMPLE_PARMS] = { .type = NL_A_UNSPEC,
+                           .min_len = sizeof(struct tc_sample),
+                           .optional = false, },
+    [TCA_SAMPLE_PSAMPLE_GROUP] = { .type = NL_A_U32,
+                                   .optional = false, },
+    [TCA_SAMPLE_RATE] = { .type = NL_A_U32,
+                          .optional = false, },
+};
+
+static int
+nl_parse_act_sample(struct nlattr *options, struct tc_flower *flower)
+{
+    struct nlattr *sample_attrs[ARRAY_SIZE(sample_policy)];
+    struct tc_action *action;
+
+    if (!nl_parse_nested(options, sample_policy, sample_attrs,
+                         ARRAY_SIZE(sample_policy))) {
+        VLOG_ERR_RL(&error_rl, "Failed to parse sample action options");
+        return EPROTO;
+    }
+
+    action = &flower->actions[flower->action_count++];
+    action->type = TC_ACT_SAMPLE;
+    action->sample.group_id =
+        nl_attr_get_u32(sample_attrs[TCA_SAMPLE_PSAMPLE_GROUP]);
+    action->sample.rate =
+        nl_attr_get_u32(sample_attrs[TCA_SAMPLE_RATE]);
+
+    return 0;
+}
+
 static const struct nl_policy mirred_policy[] = {
     [TCA_MIRRED_PARMS] = { .type = NL_A_UNSPEC,
                            .min_len = sizeof(struct tc_mirred),
@@ -1999,6 +2032,8 @@ nl_parse_single_action(struct nlattr *action, struct 
tc_flower *flower,
         nl_parse_act_ct(act_options, flower);
     } else if (!strcmp(act_kind, "police")) {
         nl_parse_act_police(act_options, flower);
+    } else if (!strcmp(act_kind, "sample")) {
+        nl_parse_act_sample(act_options, flower);
     } else {
         VLOG_ERR_RL(&error_rl, "unknown tc action kind: %s", act_kind);
         err = EINVAL;
@@ -2789,6 +2824,23 @@ nl_msg_put_act_mirred(struct ofpbuf *request, int 
ifindex, int action,
     nl_msg_end_nested(request, offset);
 }
 
+static void
+nl_msg_put_act_sample(struct ofpbuf *request, uint32_t rate, uint32_t group_id)
+{
+    size_t offset;
+
+    nl_msg_put_string(request, TCA_ACT_KIND, "sample");
+    offset = nl_msg_start_nested(request, TCA_ACT_OPTIONS | NLA_F_NESTED);
+    {
+        struct tc_sample parm = { .action = TC_ACT_PIPE };
+
+        nl_msg_put_unspec(request, TCA_SAMPLE_PARMS, &parm, sizeof parm);
+        nl_msg_put_u32(request, TCA_SAMPLE_RATE, rate);
+        nl_msg_put_u32(request, TCA_SAMPLE_PSAMPLE_GROUP, group_id);
+    }
+    nl_msg_end_nested(request, offset);
+}
+
 static inline void
 nl_msg_put_act_cookie(struct ofpbuf *request, struct tc_cookie *ck) {
     if (ck->len) {
@@ -3101,6 +3153,7 @@ get_action_index_for_tc_actions(struct tc_flower *flower, 
uint16_t act_index,
         case TC_ACT_MPLS_SET:
         case TC_ACT_GOTO:
         case TC_ACT_CT:
+        case TC_ACT_SAMPLE:
             /* Increase act_index by one if we are sure this type of action
              * will only add one tc action in the kernel. */
             act_index++;
@@ -3308,6 +3361,13 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct 
tc_flower *flower)
                 nl_msg_end_nested(request, act_offset);
             }
             break;
+            case TC_ACT_SAMPLE: {
+                act_offset = nl_msg_start_nested(request, act_index++);
+                nl_msg_put_act_sample(request, action->sample.rate,
+                                      action->sample.group_id);
+                nl_msg_end_nested(request, act_offset);
+            }
+            break;
             case TC_ACT_OUTPUT: {
                 if (!released && flower->tunnel) {
                     nl_msg_put_flower_acts_release(request, act_index++);
diff --git a/lib/tc.h b/lib/tc.h
index 0d9b1b8e7..1892b98d6 100644
--- a/lib/tc.h
+++ b/lib/tc.h
@@ -192,6 +192,7 @@ enum tc_action_type {
     TC_ACT_CT,
     TC_ACT_POLICE,
     TC_ACT_POLICE_MTU,
+    TC_ACT_SAMPLE,
 };
 
 enum nat_type {
@@ -283,6 +284,10 @@ struct tc_action {
             uint32_t result_jump;
             uint16_t mtu;
         } police;
+        struct {
+            uint32_t rate;
+            uint32_t group_id;
+        } sample;
     };
 
     enum tc_action_type type;
@@ -330,12 +335,14 @@ tc_make_tcf_id(int ifindex, uint32_t block_id, uint16_t 
prio,
 }
 
 static inline struct tcf_id
-tc_make_tcf_id_chain(int ifindex, uint32_t block_id, uint32_t chain,
-                     uint16_t prio, enum tc_qdisc_hook hook)
+tc_make_tcf_id_all(int ifindex, uint32_t block_id, uint32_t chain,
+                   uint16_t prio, enum tc_qdisc_hook hook,
+                   uint32_t sample_group_id)
 {
     struct tcf_id id = tc_make_tcf_id(ifindex, block_id, prio, hook);
 
     id.chain = chain;
+    id.sample_group_id = sample_group_id;
 
     return id;
 }
-- 
2.26.3

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to