|SUCCESS| [v2] app/test-pmd: add generic PROG action parser support
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Thu May 21 16:30:37 CEST 2026
Test-Label: ai-code-review
Test-Status: SUCCESS
https://dpdk.org/patch/164226
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-05-21
Reviewing patch: add generic PROG action parser support
commit sha: 20260521101354.726240-1-megha.ajmera at intel.com
Author: Megha Ajmera <megha.ajmera at intel.com>
Subject: app/test-pmd: add generic PROG action parser support
This patch adds parser support for a generic PROG flow action in testpmd,
enabling programmable action configuration through the flow command
interface.
> diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
> index ebc036b14b..daf1de7146 100644
> --- a/app/test-pmd/cmdline_flow.c
> +++ b/app/test-pmd/cmdline_flow.c
[ ... ]
> @@ -981,6 +988,24 @@ struct action_sample_data {
> struct rte_flow_action_sample conf;
> uint32_t idx;
> };
> +
> +#define ACTION_PROG_MAX_ARGS 32
> +#define ACTION_PROG_NAME_LEN_MAX 64
> +#define ACTION_PROG_ARG_NAME_LEN_MAX 16
> +
> +struct action_prog_argument_data {
> + char name[ACTION_PROG_ARG_NAME_LEN_MAX];
> + uint32_t length;
> + uint32_t size;
> + uint64_t value;
> +};
> +
> +struct action_prog_data {
> + char name[ACTION_PROG_NAME_LEN_MAX];
> + uint32_t args_num;
> + uint32_t length;
> + struct action_prog_argument_data args[ACTION_PROG_MAX_ARGS];
> +};
Does the 'length' field in action_prog_argument_data serve any purpose?
It appears to be set but never used in the conversion or validation logic.
[ ... ]
> +/** Parse PROG action */
> +static int
> +parse_vc_action_prog(struct context *ctx, const struct token *token,
> + const char *str, unsigned int len, void *buf,
> + unsigned int size)
> +{
> + struct buffer *out = buf;
> + struct action_prog_data *prog_data;
> + int ret;
> +
> + ret = parse_vc(ctx, token, str, len, buf, size);
> + if (ret < 0)
> + return ret;
> +
> + /* Nothing else to do if there is no buffer. */
> + if (!out)
> + return ret;
> +
> + if (!out->args.vc.actions_n)
> + return -1;
Does this code check that out->args.vc.data is non-NULL before using it
as ctx->object? The parse_vc_action_sample() function in the same file
performs this validation.
> +
> + /* Point to selected object. */
> + ctx->object = out->args.vc.data;
> + ctx->objmask = NULL;
> +
> + prog_data = ctx->object;
> + prog_data->args_num = 0;
> +
> + return ret;
> +}
[ ... ]
> +static int __prog_argument_name_args_push(struct context *ctx, const struct token *token,
> + const char *str, unsigned int len, void *buf,
> + unsigned int size, uint32_t arg_ind)
In __prog_argument_name_args_push(), __prog_argument_size_args_push(),
and __prog_argument_value_args_push(), are the static arrays arg_addr[],
arg_len[], arg_data[], and arg[] thread-safe? Can testpmd's flow command
parsing be invoked concurrently from multiple threads?
> +{
> + static struct arg arg_addr[ACTION_PROG_MAX_ARGS];
> + static struct arg arg_len[ACTION_PROG_MAX_ARGS];
> + static struct arg arg_data[ACTION_PROG_MAX_ARGS];
[ ... ]
> +static int
> +convert_action_prog_to_rte_flow(struct rte_flow_action *actions,
> + uint32_t prog_action_count,
> + uint32_t *converted_idx,
> + uint32_t converted_idx_cap,
> + uint32_t *converted_idx_num)
> +{
> + uint32_t i = 0;
> + uint32_t j;
> + uint32_t k;
> + const struct action_prog_data *prog_data;
> + struct rte_flow_action_prog *prog;
> + struct rte_flow_action_prog_argument *args;
> + uint8_t *value;
> + bool arg_error;
> + int ret = 0;
> +
> + if (converted_idx_num)
> + *converted_idx_num = 0;
> +
> + /* If action_count is 0, count the actions until END action */
> + if (prog_action_count == 0) {
> + while (actions[i].type != RTE_FLOW_ACTION_TYPE_END)
> + i++;
> + prog_action_count = i + 1; /* Include END action */
> + i = 0; /* Reset counter for the processing loop */
> + }
Does this loop have bounds checking for the actions array? Can an
unterminated actions array cause this loop to overflow?
> +
> + /* Process all actions */
> + for (i = 0; i < prog_action_count; i++) {
> + if (actions[i].type == RTE_FLOW_ACTION_TYPE_PROG) {
> + prog_data = (const struct action_prog_data *)actions[i].conf;
> + if (!prog_data) {
> + fprintf(stderr, "Prog action found but no data provided\n");
> + ret = -EINVAL;
> + continue;
> + }
> +
> + prog = calloc(1, sizeof(struct rte_flow_action_prog));
> + if (!prog) {
> + fprintf(stderr, "Failed to allocate memory for prog action\n");
> + ret = -ENOMEM;
> + continue;
> + }
> +
> + prog->name = strdup(prog_data->name);
> + if (!prog->name) {
> + fprintf(stderr, "Failed to allocate memory for prog name\n");
> + free(prog);
> + ret = -ENOMEM;
> + continue;
> + }
In convert_action_prog_to_rte_flow(), when allocation failures occur
midway through the loop and ret is set to -ENOMEM or -EINVAL with
continue, does this leave previously converted actions without cleanup?
The function returns ret but previously allocated resources from earlier
loop iterations remain in actions[].conf without being freed.
> +
> + prog->args_num = prog_data->args_num;
> +
> + if (prog->args_num > 0) {
> + args = calloc(prog->args_num,
> + sizeof(struct rte_flow_action_prog_argument));
> + if (!args) {
> + fprintf(stderr,
> + "Failed to allocate memory for prog arguments\n");
> + free((void *)(uintptr_t)prog->name);
> + free(prog);
> + ret = -ENOMEM;
> + continue;
> + }
> +
> + arg_error = false;
> + for (j = 0; j < prog->args_num; j++) {
> + args[j].name = strdup(prog_data->args[j].name);
> + if (!args[j].name) {
> + fprintf(stderr,
> + "Failed to allocate memory for argument name\n");
> + ret = -ENOMEM;
> + arg_error = true;
> + break;
> + }
> +
> + args[j].size = prog_data->args[j].size;
> + if (args[j].size == 0)
> + continue;
> + if (args[j].size > sizeof(prog_data->args[j].value)) {
> + free((void *)(uintptr_t)args[j].name);
> + arg_error = true;
> + ret = -EINVAL;
> + break;
> + }
> +
> + value = malloc(args[j].size);
> + if (!value) {
> + fprintf(stderr,
> + "Failed to allocate memory for argument value\n");
> + free((void *)(uintptr_t)args[j].name);
> + ret = -ENOMEM;
> + arg_error = true;
> + break;
> + }
> +
> + memcpy(value,
> + &prog_data->args[j].value,
> + args[j].size);
> + args[j].value = value;
> + }
> +
> + if (arg_error) {
> + /* Free all allocated resources */
> + for (k = 0; k < j; k++) {
> + free((void *)(uintptr_t)args[k].name);
> + free((void *)(uintptr_t)args[k].value);
> + }
> + free(args);
> + free((void *)(uintptr_t)prog->name);
> + free(prog);
> + continue;
> + }
> +
> + prog->args = args;
> + }
> +
> + actions[i].conf = prog;
> + if (converted_idx && converted_idx_num &&
> + *converted_idx_num < converted_idx_cap)
> + converted_idx[(*converted_idx_num)++] = i;
> + }
> + }
> +
> + return ret;
> +}
[ ... ]
> @@ -13561,9 +14112,36 @@ cmd_flow_parsed(const struct buffer *in)
> &in->args.vc.tunnel_ops);
> break;
> case CREATE:
> - port_flow_create(in->port, &in->args.vc.attr,
> - in->args.vc.pattern, in->args.vc.actions,
> - &in->args.vc.tunnel_ops, in->args.vc.user_id);
> + {
> + uint32_t *converted_idx;
> + uint32_t converted_idx_num = 0;
> +
> + converted_idx = calloc(in->args.vc.actions_n,
> + sizeof(*converted_idx));
> + if (!converted_idx) {
> + fprintf(stderr,
> + "Warning: Failed to allocate conversion index list\n");
> + break;
> + }
> +
> + /* Convert from action_prog_data to rte_flow_action_prog. */
> + ret = convert_action_prog_to_rte_flow(in->args.vc.actions,
> + 0,
> + converted_idx,
> + in->args.vc.actions_n,
> + &converted_idx_num);
> + if (ret < 0)
> + fprintf(stderr,
> + "Warning: Failed to convert program action data: %s\n",
> + strerror(-ret));
> + port_flow_create(in->port, &in->args.vc.attr,
> + in->args.vc.pattern, in->args.vc.actions,
> + &in->args.vc.tunnel_ops, in->args.vc.user_id);
Does this code call port_flow_create() even when
convert_action_prog_to_rte_flow() returns an error? Should it skip the
call to port_flow_create() if conversion failed?
> + free_action_prog_converted(in->args.vc.actions,
> + converted_idx,
> + converted_idx_num);
> + free(converted_idx);
> + }
> break;
> case DESTROY:
> port_flow_destroy(in->port, in->args.destroy.rule_n,
More information about the test-report
mailing list