On 19 Jun 2023, at 7:01, Chris Mi wrote:
> <SNIP> >>> + if (err) { >>> + VLOG_ERR_RL(&error_rl, "No OVS_ACTION_ATTR_USERSPACE attribute"); >>> + } >>> + return err; >>> +} >>> + >>> +static void >>> +offload_sample_init(struct offload_sample *sample, >>> + const struct nlattr *next_actions, >>> + size_t next_actions_len, >>> + struct tc_flower *flower, >>> + const struct flow_tnl *tnl) >>> +{ >>> + memset(sample, 0, sizeof *sample); >>> + if (flower->tunnel) { >>> + sample->tunnel = CONST_CAST(struct flow_tnl *, tnl); >>> + } >>> + sample->userspace_actions = CONST_CAST(struct nlattr *, next_actions); >>> + sample->userspace_actions_len = next_actions_len; >> Here we should initialize the userspace_actions, to make it work :) >> This was previously in the clone action in patch 3: >> >> @@ -2148,8 +2148,11 @@ offload_sample_init(struct offload_sample *sample, >> if (flower->tunnel) { >> sample->tunnel = CONST_CAST(struct flow_tnl *, tnl); >> } >> - sample->userspace_actions = CONST_CAST(struct nlattr *, next_actions); >> - sample->userspace_actions_len = next_actions_len; >> + >> + sample->userspace_actions = xmalloc(next_actions_len + NLA_HDRLEN); >> + nullable_memcpy((char *) sample->userspace_actions + NLA_HDRLEN, >> + next_actions, next_actions_len); >> + sample->userspace_actions->nla_len = next_actions_len + NLA_HDRLEN; >> sample->ifindex = flower->ifindex; >> >> One remaining question is, should we set the nla_type here to >> OVS_ACTION_ATTR_SAMPLE or OVS_ACTION_ATTR_USERSPACE as this what they are, >> or is it safe to not set this as the upcall handling will ignore the type? > According to function dpif_get_actions(), nla_type is ignored: > > if (upcall->actions) { > /* Actions were passed up from datapath. */ > *actions = nl_attr_get(upcall->actions); > actions_len = nl_attr_get_size(upcall->actions); > } Thanks! I guess I could have done this myself :( Cheers, Eelco Reviewing your v28 right now. And doing some final testing! >> >>> + sample->ifindex = flower->ifindex; >>> +} >>> + >>> +static int >>> +parse_sample_action(struct tc_flower *flower, struct tc_action *tc_action, >>> + const struct nlattr *next_actions, size_t >>> next_actions_len, >>> + const struct nlattr *sample_action, >>> + const struct flow_tnl *tnl) >>> +{ >>> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); >>> + struct offload_sample sample; >>> + const struct nlattr *nla; >>> + unsigned int left; >>> + uint32_t sgid; >>> + int err; >> As you change the function, you should set err to EINVAL here :) >> >>> + >>> + offload_sample_init(&sample, next_actions, next_actions_len, flower, >>> tnl); >>> + sample.action = CONST_CAST(struct nlattr *, sample_action); >>> + >>> + err = EINVAL; >> Remove this here, as you no longer override it with offload_sample_init() >> >>> + NL_NESTED_FOR_EACH_UNSAFE (nla, left, sample_action) { >>> + if (nl_attr_type(nla) == OVS_SAMPLE_ATTR_ACTIONS) { >>> + err = parse_sample_actions_attribute(nla, &sample); >>> + if (err) { >>> + break; >>> + } >>> + } 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); >>> + if (!tc_action->sample.rate) { >>> + break; >>> + } >>> + } else { >> Here you removed err = EINVAL; but it should be here if you want to error >> out on unsupported attributes. >> >> I re-wrote the function a bit, to clean it up, and made it look a bit more >> like the v18 version. I removed the conditional breaks in the if() branches, >> as it made it looks like we check for errors in these branch, but in fact >> the checks are there to verify the attributes where present. >> Also made sure we do not trash the tc_action structure on failure. >> >> static int >> parse_sample_action(struct tc_flower *flower, struct tc_action *tc_action, >> const struct nlattr *next_actions, size_t >> next_actions_len, >> const struct nlattr *sample_action, >> const struct flow_tnl *tnl) >> { >> static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); >> struct offload_sample sample; >> const struct nlattr *nla; >> unsigned int left; >> uint32_t rate = 0; >> int ret = EINVAL; >> uint32_t sgid; >> >> offload_sample_init(&sample, next_actions, next_actions_len, flower, >> tnl); >> sample.action = CONST_CAST(struct nlattr *, sample_action); >> >> NL_NESTED_FOR_EACH_UNSAFE (nla, left, sample_action) { >> if (nl_attr_type(nla) == OVS_SAMPLE_ATTR_ACTIONS) { >> ret = parse_sample_actions_attribute(nla, &sample); >> } else if (nl_attr_type(nla) == OVS_SAMPLE_ATTR_PROBABILITY) { >> rate = UINT32_MAX / nl_attr_get_u32(nla); >> } else { >> return EINVAL; >> } >> } >> >> /* This check makes sure both attributes above were present and valid. >> */ >> if (!rate || ret) { >> return EINVAL; >> } >> >> sgid = sgid_alloc(&sample); >> if (!sgid) { >> VLOG_DBG_RL(&rl, "Failed allocating group id for sample action"); >> return ENOENT; >> } >> >> tc_action->type = TC_ACT_SAMPLE; >> tc_action->sample.rate = rate; >> tc_action->sample.group_id = sgid; >> flower->action_count++; >> >> return 0; >> } >> >>> + break; >>> + } >>> + } >>> + >>> + if (!tc_action->sample.rate || err) { >>> + return EINVAL; >>> + } >>> + >>> + sgid = sgid_alloc(&sample); >>> + if (!sgid) { >>> + VLOG_DBG_RL(&rl, "Failed allocating group id for sample action"); >>> + return ENOENT; >>> + } >>> + tc_action->sample.group_id = sgid; >>> + flower->action_count++; >>> + >>> + return err; >>> +} >>> + >>> +static int >>> +parse_userspace_action(struct tc_flower *flower, struct tc_action >>> *tc_action, >>> + const struct nlattr *next_actions, >>> + size_t next_actions_len, >>> + const struct nlattr *userspace_action, >>> + const struct flow_tnl *tnl) >>> +{ >>> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); >>> + struct offload_sample sample; >>> + uint32_t sgid; >>> + int err; >>> + >>> + offload_sample_init(&sample, next_actions, next_actions_len, flower, >>> tnl); >>> + >>> + /* 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, &sample); >>> + if (err) { >>> + return err; >>> + } >>> + sample.action = (struct nlattr *) userspace_action; >>> + sgid = sgid_alloc(&sample); >>> + if (!sgid) { >>> + VLOG_DBG_RL(&rl, "Failed allocating group id for sample action"); >>> + return ENOENT; >>> + } >>> + tc_action->type = TC_ACT_SAMPLE; >>> + tc_action->sample.group_id = sgid; >>> + tc_action->sample.rate = 1; >>> + flower->action_count++; >>> + >>> + return err; >> Here we can just return 0. >> >>> +} >>> >>> static int >>> parse_check_pkt_len_action(struct netdev *netdev, struct tc_flower >>> *flower, >>> - struct offload_info *info, struct tc_action >>> *action, >>> - const struct nlattr *nla, bool last_action, >>> + struct offload_info *info, >>> + const struct flow_tnl *tnl, >>> + struct tc_action *action, const struct nlattr >>> *nla, >>> + bool last_action, >>> struct tc_action **need_jump_update, >>> bool *recirc_act) >>> { >>> @@ -2070,7 +2283,7 @@ parse_check_pkt_len_action(struct netdev *netdev, >>> struct tc_flower *flower, >>> * NOTE: The last_action parameter means that there are no more >>> actions >>> * after the if () then ... else () case. */ >>> nl_actions = a[OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER]; >>> - err = netdev_tc_parse_nl_actions(netdev, flower, info, >>> + err = netdev_tc_parse_nl_actions(netdev, flower, info, tnl, >>> nl_attr_get(nl_actions), >>> nl_attr_get_size(nl_actions), >>> recirc_act, !last_action, >>> @@ -2086,7 +2299,7 @@ parse_check_pkt_len_action(struct netdev *netdev, >>> struct tc_flower *flower, >>> >>> /* Parse and add the less than action(s). */ >>> nl_actions = a[OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL]; >>> - err = netdev_tc_parse_nl_actions(netdev, flower, info, >>> + err = netdev_tc_parse_nl_actions(netdev, flower, info, tnl, >>> nl_attr_get(nl_actions), >>> nl_attr_get_size(nl_actions), >>> recirc_act, !last_action, >>> @@ -2139,6 +2352,7 @@ parse_check_pkt_len_action(struct netdev *netdev, >>> struct tc_flower *flower, >>> static int >>> netdev_tc_parse_nl_actions(struct netdev *netdev, struct tc_flower >>> *flower, >>> struct offload_info *info, >>> + const struct flow_tnl *tnl, >>> const struct nlattr *actions, size_t >>> actions_len, >>> bool *recirc_act, bool more_actions, >>> struct tc_action **need_jump_update) >>> @@ -2268,7 +2482,8 @@ netdev_tc_parse_nl_actions(struct netdev *netdev, >>> struct tc_flower *flower, >>> action->police.index = police_index; >>> flower->action_count++; >>> } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_CHECK_PKT_LEN) { >>> - err = parse_check_pkt_len_action(netdev, flower, info, action, >>> nla, >>> + err = parse_check_pkt_len_action(netdev, flower, info, tnl, >>> + action, nla, >>> nl_attr_len_pad(nla, >>> left) >= left >>> && !more_actions, >>> @@ -2277,14 +2492,28 @@ netdev_tc_parse_nl_actions(struct netdev *netdev, >>> struct tc_flower *flower, >>> if (err) { >>> return err; >>> } >>> - } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_SAMPLE) { >>> - struct offload_sample sample; >>> - uint32_t sgid; >>> - >>> - memset(&sample, 0, sizeof sample); >>> - sgid = sgid_alloc(&sample); >>> - sgid_free(sgid); >>> - return EOPNOTSUPP; >>> + } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_SAMPLE || >>> + 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; >>> + } >>> + if (get_flower_sgid_count(flower) >= MAX_TC_SAMPLES_PER_FLOW) { >>> + VLOG_ERR_RL(&error_rl, "Only %u TC_SAMPLE action per " >>> + "flow is supported", MAX_TC_SAMPLES_PER_FLOW); >>> + return EOPNOTSUPP; >>> + } >>> + if (nl_attr_type(nla) == OVS_ACTION_ATTR_SAMPLE) { >>> + err = parse_sample_action(flower, action, actions, >>> actions_len, >>> + nla, tnl); >>> + } else { >>> + err = parse_userspace_action(flower, action, actions, >>> + actions_len, nla, tnl); >>> + } >>> + if (err) { >>> + return err; >>> + } >>> } else { >>> VLOG_DBG_RL(&rl, "unsupported put action type: %d", >>> nl_attr_type(nla)); >>> @@ -2324,6 +2553,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct >>> match *match, >>> } >>> >>> memset(&flower, 0, sizeof flower); >>> + flower.ifindex = ifindex; >>> >>> chain = key->recirc_id; >>> mask->recirc_id = 0; >>> @@ -2589,16 +2819,17 @@ netdev_tc_flow_put(struct netdev *netdev, struct >>> match *match, >>> } >>> >>> /* Parse all (nested) actions. */ >>> - err = netdev_tc_parse_nl_actions(netdev, &flower, info, >>> + err = netdev_tc_parse_nl_actions(netdev, &flower, info, tnl, >>> actions, actions_len, &recirc_act, >>> false, NULL); >>> if (err) { >>> - return err; >>> + goto error_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 error_out; >>> } >>> >>> memset(&adjust_stats, 0, sizeof adjust_stats); >>> @@ -2612,7 +2843,8 @@ 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 error_out; >>> } >>> >>> flower.act_cookie.data = ufid; >>> @@ -2621,14 +2853,20 @@ netdev_tc_flow_put(struct netdev *netdev, struct >>> match *match, >>> block_id = get_block_id_from_netdev(netdev); >>> id = tc_make_tcf_id_chain(ifindex, block_id, chain, prio, hook); >>> 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 error_out; >>> } >>> >>> + if (stats) { >>> + memset(stats, 0, sizeof *stats); >>> + netdev_tc_adjust_stats(stats, &adjust_stats); >>> + } >>> + >>> + add_ufid_tc_mapping(netdev, ufid, &id, &adjust_stats, &flower); >>> + return 0; >>> + >>> +error_out: >>> + free_all_flower_sgids(&flower); >>> return err; >>> } >>> >>> diff --git a/lib/tc.c b/lib/tc.c >>> index 5c32c6f97..28ca6325a 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; >>> @@ -2791,6 +2826,24 @@ 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, >>> + uint32_t action_pc) >>> +{ >>> + 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 = action_pc }; >>> + >>> + 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) { >>> @@ -3103,6 +3156,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++; >>> @@ -3310,6 +3364,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, action_pc); >>> + 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 cdd3b4f60..5f6e15d5c 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; >>> @@ -380,6 +385,7 @@ struct tc_flower { >>> enum tc_offloaded_state offloaded_state; >>> /* Used to force skip_hw when probing tc features. */ >>> enum tc_offload_policy tc_policy; >>> + uint16_t ifindex; >>> }; >>> >>> int tc_replace_flower(struct tcf_id *id, struct tc_flower *flower); >>> -- >>> 2.26.3 >> Here is a full diff of the suggested changes, if you sent a v28 with only >> these changes I can ack the patch (need an answer on one question): >> >> diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c >> index 14a4cb393..71ec8ef1f 100644 >> --- a/lib/netdev-offload-tc.c >> +++ b/lib/netdev-offload-tc.c >> @@ -2146,8 +2146,11 @@ offload_sample_init(struct offload_sample *sample, >> if (flower->tunnel) { >> sample->tunnel = CONST_CAST(struct flow_tnl *, tnl); >> } >> - sample->userspace_actions = CONST_CAST(struct nlattr *, next_actions); >> - sample->userspace_actions_len = next_actions_len; >> + >> + sample->userspace_actions = xmalloc(next_actions_len + NLA_HDRLEN); >> + nullable_memcpy((char *) sample->userspace_actions + NLA_HDRLEN, >> + next_actions, next_actions_len); >> + sample->userspace_actions->nla_len = next_actions_len + NLA_HDRLEN; >> sample->ifindex = flower->ifindex; >> } >> >> @@ -2161,31 +2164,25 @@ parse_sample_action(struct tc_flower *flower, struct >> tc_action *tc_action, >> struct offload_sample sample; >> const struct nlattr *nla; >> unsigned int left; >> + uint32_t rate = 0; >> + int ret = EINVAL; >> uint32_t sgid; >> - int err; >> >> offload_sample_init(&sample, next_actions, next_actions_len, flower, >> tnl); >> sample.action = CONST_CAST(struct nlattr *, sample_action); >> >> - err = EINVAL; >> NL_NESTED_FOR_EACH_UNSAFE (nla, left, sample_action) { >> if (nl_attr_type(nla) == OVS_SAMPLE_ATTR_ACTIONS) { >> - err = parse_sample_actions_attribute(nla, &sample); >> - if (err) { >> - break; >> - } >> + ret = parse_sample_actions_attribute(nla, &sample); >> } 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); >> - if (!tc_action->sample.rate) { >> - break; >> - } >> + rate = UINT32_MAX / nl_attr_get_u32(nla); >> } else { >> - break; >> + return EINVAL; >> } >> } >> >> - if (!tc_action->sample.rate || err) { >> + /* This check makes sure both attributes above were present and valid. >> */ >> + if (!rate || ret) { >> return EINVAL; >> } >> >> @@ -2194,10 +2191,13 @@ parse_sample_action(struct tc_flower *flower, struct >> tc_action *tc_action, >> VLOG_DBG_RL(&rl, "Failed allocating group id for sample action"); >> return ENOENT; >> } >> + >> + tc_action->type = TC_ACT_SAMPLE; >> + tc_action->sample.rate = rate; >> tc_action->sample.group_id = sgid; >> flower->action_count++; >> >> - return err; >> + return 0; >> } >> >> static int >> @@ -2232,7 +2232,7 @@ parse_userspace_action(struct tc_flower *flower, >> struct tc_action *tc_action, >> tc_action->sample.rate = 1; >> flower->action_count++; >> >> - return err; >> + return 0; >> } >> >> static int >> _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev