@@ -981,6 +981,18 @@ parse_tc_flower_to_match(struct tc_flower *flower,
action = flower->actions;
for (i = 0; i < flower->action_count; i++, action++) {
switch (action->type) {
+ case TC_ACT_SAMPLE: {
+ const struct gid_node *node;
+
+ node = gid_find(action->sample.action_group_id);
+ if (!node) {
+ VLOG_ERR_RL(&error_rl, "gid node is NULL, gid: %d",
+ action->sample.action_group_id);
+ return ENOENT;
+ }
+ nl_msg_put(buf, node->sflow.sflow, node->sflow.sflow_len);
+ }
+ break;
case TC_ACT_VLAN_POP: {
nl_msg_put_flag(buf, OVS_ACTION_ATTR_POP_VLAN);
}
@@ -1660,6 +1672,78 @@ flower_match_to_tun_opt(struct tc_flower *flower, const struct flow_tnl *tnl,
flower->mask.tunnel.metadata.present.len = tnl->metadata.present.len;
}
+static int
+parse_userspace_userdata(const struct nlattr *actions,
+ struct dpif_sflow_attr *sflow_attr)
+{
+ 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 = CONST_CAST(struct user_action_cookie *, nl_attr_get(nla));
+ if (cookie->type == USER_ACTION_COOKIE_SFLOW) {
+ sflow_attr->userdata = CONST_CAST(void *, nl_attr_get(nla));
+ sflow_attr->userdata_len = nl_attr_get_size(nla);
+ return 0;
+ }
+ }
+ }
+
+ VLOG_ERR_RL(&error_rl, "%s: no sFlow cookie", __func__);
+ return EINVAL;
+}
+
+static int
+parse_action_userspace(const struct nlattr *actions,
+ struct dpif_sflow_attr *sflow_attr)
+{
+ const struct nlattr *nla;
+ unsigned int left;
+
+ NL_NESTED_FOR_EACH_UNSAFE (nla, left, actions) {
+ if (nl_attr_type(nla) == OVS_ACTION_ATTR_USERSPACE) {
+ return parse_userspace_userdata(nla, sflow_attr);
+ }
+ }
+
+ VLOG_ERR_RL(&error_rl, "%s: no OVS_ACTION_ATTR_USERSPACE attribute",
+ __func__);
+ return EINVAL;
+}
+
+static int
+parse_sample_action(const struct nlattr *actions,
+ struct dpif_sflow_attr *sflow_attr,
+ struct tc_action *tc_action)
+{
+ const struct nlattr *nla;
+ unsigned int left;
+ int ret = EINVAL;
+
+ sflow_attr->sflow = actions;
+ sflow_attr->sflow_len = actions->nla_len;
+
+ NL_NESTED_FOR_EACH_UNSAFE (nla, left, actions) {
+ if (nl_attr_type(nla) == OVS_SAMPLE_ATTR_ACTIONS) {
+ ret = parse_action_userspace(nla, sflow_attr);
+ } else if (nl_attr_type(nla) == OVS_SAMPLE_ATTR_PROBABILITY) {
+ tc_action->type = TC_ACT_SAMPLE;
+ tc_action->sample.action_rate = UINT32_MAX / nl_attr_get_u32(nla);
+ } else {
+ return EINVAL;
+ }
+ }
+
+ if (tc_action->sample.action_rate) {
+ return ret;
+ }
+
+ return EINVAL;
+}
+
static int
netdev_tc_flow_put(struct netdev *netdev, struct match *match,
struct nlattr *actions, size_t actions_len,
@@ -1676,6 +1760,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
struct tc_action *action;
bool recirc_act = false;
uint32_t block_id = 0;
+ uint32_t group_id = 0;
struct nlattr *nla;
struct tcf_id id;
uint32_t chain;
@@ -1965,7 +2050,8 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
NL_ATTR_FOR_EACH(nla, left, actions, actions_len) {
if (flower.action_count >= TCA_ACT_MAX_NUM) {
VLOG_DBG_RL(&rl, "Can only support %d actions", TCA_ACT_MAX_NUM);
- return EOPNOTSUPP;
+ err = EOPNOTSUPP;
+ goto out;
}
action = &flower.actions[flower.action_count];
if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
@@ -1975,7 +2061,8 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
if (!outdev) {
VLOG_DBG_RL(&rl, "Can't find netdev for output port %d", port);
- return ENODEV;
+ err = ENODEV;
+ goto out;
}
action->out.ifindex_out = netdev_get_ifindex(outdev);
action->out.ingress = is_internal_port(netdev_get_type(outdev));
@@ -2013,7 +2100,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
err = parse_put_flow_set_action(&flower, action, set, set_len);
if (err) {
- return err;
+ goto out;
}
if (action->type == TC_ACT_ENCAP) {
action->encap.tp_dst = info->tp_dst_port;
@@ -2026,7 +2113,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
err = parse_put_flow_set_masked_action(&flower, action, set,
set_len, true);
if (err) {
- return err;
+ goto out;
}
} else if (nl_attr_type(nla) == OVS_ACTION_ATTR_CT) {
const struct nlattr *ct = nl_attr_get(nla);
@@ -2034,7 +2121,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
err = parse_put_flow_ct_action(&flower, action, ct, ct_len);
if (err) {
- return err;
+ goto out;
}
} else if (nl_attr_type(nla) == OVS_ACTION_ATTR_CT_CLEAR) {
action->type = TC_ACT_CT;
@@ -2053,17 +2140,49 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
struct dpif_sflow_attr sflow_attr;
memset(&sflow_attr, 0, sizeof sflow_attr);
- gid_alloc_ctx(&sflow_attr);
+ if (flower.tunnel) {
+ sflow_attr.tunnel = CONST_CAST(struct flow_tnl *, tnl);
+ }
+ err = parse_sample_action(nla, &sflow_attr, action);
+ if (err) {
+ goto out;
+ }
+ group_id = gid_alloc_ctx(&sflow_attr);
+ action->sample.action_group_id = group_id;
+ flower.action_count++;
+ } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_USERSPACE) {
+ struct dpif_sflow_attr sflow_attr;
+
+ /* If there is a sFlow cookie inside of a userspace attribute,
+ * but no sample attribute, that means sampling rate is 1.
+ */
+ memset(&sflow_attr, 0, sizeof sflow_attr);
+ if (flower.tunnel) {
+ sflow_attr.tunnel = CONST_CAST(struct flow_tnl *, tnl);
+ }
+ err = parse_userspace_userdata(nla, &sflow_attr);
+ if (err) {
+ goto out;
+ }
+ sflow_attr.sflow = nla;
+ sflow_attr.sflow_len = nla->nla_len;
+ group_id = gid_alloc_ctx(&sflow_attr);
+ action->type = TC_ACT_SAMPLE;
+ action->sample.action_group_id = group_id;
+ action->sample.action_rate = 1;
+ flower.action_count++;
} else {
VLOG_DBG_RL(&rl, "unsupported put action type: %d",
nl_attr_type(nla));
- return EOPNOTSUPP;
+ err = EOPNOTSUPP;
+ goto out;
}
}
if ((chain || recirc_act) && !info->recirc_id_shared_with_tc) {
VLOG_ERR_RL(&error_rl, "flow_put: recirc_id sharing not supported");
- return EOPNOTSUPP;
+ err = EOPNOTSUPP;
+ goto out;
}
if (get_ufid_tc_mapping(ufid, &id) == 0) {
@@ -2075,20 +2194,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_chain(ifindex, block_id, chain, prio, hook, group_id);
err = tc_replace_flower(&id, &flower);
if (!err) {
if (stats) {
memset(stats, 0, sizeof *stats);
}
add_ufid_tc_mapping(netdev, ufid, &id);
+ } else {
+ goto out;
+ }
+
+ return 0;
+
+out:
+ if (group_id) {
+ gid_free(group_id);
}
return err;
@@ -2298,6 +2427,15 @@ netdev_tc_init_flow_api(struct netdev *netdev)
return 0;
}
+static const struct dpif_sflow_attr *
+netdev_tc_sflow_attr_get(uint32_t gid)
+{
+ const struct gid_node *node;
+
+ node = gid_find(gid);
+ return node ? &node->sflow : NULL;
+}
+
const struct netdev_flow_api netdev_offload_tc = {
.type = "linux_tc",
.flow_flush = netdev_tc_flow_flush,
@@ -2308,4 +2446,5 @@ const struct netdev_flow_api netdev_offload_tc = {
.flow_get = netdev_tc_flow_get,
.flow_del = netdev_tc_flow_del,
.init_flow_api = netdev_tc_init_flow_api,
+ .sflow_attr_get = netdev_tc_sflow_attr_get,
};
@@ -31,6 +31,7 @@
#include <linux/tc_act/tc_tunnel_key.h>
#include <linux/tc_act/tc_vlan.h>
#include <linux/tc_act/tc_ct.h>
+#include <linux/tc_act/tc_sample.h>
#include <linux/gen_stats.h>
#include <net/if.h>
#include <unistd.h>
@@ -1289,6 +1290,38 @@ nl_parse_act_gact(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.action_group_id =
+ nl_attr_get_u32(sample_attrs[TCA_SAMPLE_PSAMPLE_GROUP]);
+ action->sample.action_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),
@@ -1697,6 +1730,8 @@ nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
/* Added for TC rule only (not in OvS rule) so ignore. */
} else if (!strcmp(act_kind, "ct")) {
nl_parse_act_ct(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;
@@ -2292,6 +2327,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) {
@@ -2551,6 +2603,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.action_rate,
+ action->sample.action_group_id);
+ nl_msg_end_nested(request, act_offset);
+ }
+ break;
case TC_ACT_OUTPUT: {
if (!released && flower->tunnel) {
act_offset = nl_msg_start_nested(request, act_index++);
@@ -171,6 +171,7 @@ enum tc_action_type {
TC_ACT_MPLS_SET,
TC_ACT_GOTO,
TC_ACT_CT,
+ TC_ACT_SAMPLE,
};
enum nat_type {
@@ -253,6 +254,11 @@ struct tc_action {
bool force;
bool commit;
} ct;
+
+ struct {
+ uint32_t action_rate;
+ uint32_t action_group_id;
+ } sample;
};
enum tc_action_type type;
@@ -292,11 +298,12 @@ 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)
+ uint16_t prio, enum tc_qdisc_hook hook, uint32_t group_id)
{
struct tcf_id id = tc_make_tcf_id(ifindex, block_id, prio, hook);
id.chain = chain;
+ id.sflow_group_id = group_id;
return id;
}