Please see inline.

> -----Original Message-----
> From: sk...@marvell.com <sk...@marvell.com>
> Sent: Friday, September 8, 2023 4:19 PM
> To: Thomas Monjalon <tho...@monjalon.net>; Sunil Kumar Kori 
> <sk...@marvell.com>;
> Rakesh Kudurumalla <rkuduruma...@marvell.com>
> Cc: dev@dpdk.org
> Subject: [EXT] [PATCH v3 1/1] app/graph: add example for different usecases
> 
> External Email
> 
> ----------------------------------------------------------------------
> From: Sunil Kumar Kori <sk...@marvell.com>
> 
> Current l3fwd-graph application only validates l3fwd use case.
> To scale up this, new application will be added with a framework
> to run as user's provided usecases.
> 
> Required configuration and use cases details are fetched via a
> static .cli file which will be used to create a graph for
> requested uscases.
> 
> Signed-off-by: Sunil Kumar Kori <sk...@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkuduruma...@marvell.com>
> ---
>  MAINTAINERS                                  |   7 +
>  app/graph/cli.c                              | 208 ++++++
>  app/graph/cli.h                              |  48 ++
>  app/graph/cli_priv.h                         |  19 +
>  app/graph/conn.c                             | 284 +++++++++
>  app/graph/conn.h                             |  46 ++
>  app/graph/ethdev.c                           | 632 +++++++++++++++++++
>  app/graph/ethdev.h                           |  28 +
>  app/graph/ethdev_priv.h                      |  46 ++
>  app/graph/ethdev_rx.c                        | 139 ++++
>  app/graph/ethdev_rx.h                        |  32 +
>  app/graph/ethdev_rx_priv.h                   |  23 +
>  app/graph/examples/l3fwd.cli                 |  87 +++
>  app/graph/graph.c                            | 383 +++++++++++
>  app/graph/graph.h                            |  11 +
>  app/graph/graph_priv.h                       |  32 +
>  app/graph/ip4_route.c                        | 146 +++++
>  app/graph/ip6_route.c                        | 154 +++++
>  app/graph/l3fwd.c                            | 152 +++++
>  app/graph/l3fwd.h                            |  11 +
>  app/graph/main.c                             | 201 ++++++
>  app/graph/mempool.c                          | 134 ++++
>  app/graph/mempool.h                          |  18 +
>  app/graph/mempool_priv.h                     |  16 +
>  app/graph/meson.build                        |  25 +
>  app/graph/module_api.h                       |  33 +
>  app/graph/neigh.c                            | 269 ++++++++
>  app/graph/neigh.h                            |  11 +
>  app/graph/neigh_priv.h                       |  22 +
>  app/graph/route.h                            |  30 +
>  app/graph/utils.c                            | 155 +++++
>  app/graph/utils.h                            |  14 +
>  app/meson.build                              |   1 +
>  doc/guides/tools/graph.rst                   | 171 +++++
>  doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 ++++++
>  doc/guides/tools/index.rst                   |   1 +
>  36 files changed, 3799 insertions(+)
>  create mode 100644 app/graph/cli.c
>  create mode 100644 app/graph/cli.h
>  create mode 100644 app/graph/cli_priv.h
>  create mode 100644 app/graph/conn.c
>  create mode 100644 app/graph/conn.h
>  create mode 100644 app/graph/ethdev.c
>  create mode 100644 app/graph/ethdev.h
>  create mode 100644 app/graph/ethdev_priv.h
>  create mode 100644 app/graph/ethdev_rx.c
>  create mode 100644 app/graph/ethdev_rx.h
>  create mode 100644 app/graph/ethdev_rx_priv.h
>  create mode 100644 app/graph/examples/l3fwd.cli
>  create mode 100644 app/graph/graph.c
>  create mode 100644 app/graph/graph.h
>  create mode 100644 app/graph/graph_priv.h
>  create mode 100644 app/graph/ip4_route.c
>  create mode 100644 app/graph/ip6_route.c
>  create mode 100644 app/graph/l3fwd.c
>  create mode 100644 app/graph/l3fwd.h
>  create mode 100644 app/graph/main.c
>  create mode 100644 app/graph/mempool.c
>  create mode 100644 app/graph/mempool.h
>  create mode 100644 app/graph/mempool_priv.h
>  create mode 100644 app/graph/meson.build
>  create mode 100644 app/graph/module_api.h
>  create mode 100644 app/graph/neigh.c
>  create mode 100644 app/graph/neigh.h
>  create mode 100644 app/graph/neigh_priv.h
>  create mode 100644 app/graph/route.h
>  create mode 100644 app/graph/utils.c
>  create mode 100644 app/graph/utils.h
>  create mode 100644 doc/guides/tools/graph.rst
>  create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg
> 

[Nithin] Split to multiple smaller patches

> diff --git a/MAINTAINERS b/MAINTAINERS
> index 698608cdb2..7f149bd060 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1806,6 +1806,13 @@ F: dts/
>  F: devtools/dts-check-format.sh
>  F: doc/guides/tools/dts.rst
> 
> +Graph application
> +M: Sunil Kumar Kori <sk...@marvell.com>
> +M: Rakesh Kudurumalla <rkuduruma...@marvell.com>
> +F: app/graph/
> +F: doc/guides/tools/graph.rst
> +F: doc/guides/tools/img/graph-usecase-l3fwd.svg
> +
> 
>  Other Example Applications
>  --------------------------
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> new file mode 100644
> index 0000000000..237fa8008f
> --- /dev/null
> +++ b/app/graph/cli.c
> @@ -0,0 +1,208 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <ctype.h>
> +#include <stdio.h>
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <rte_common.h>
> +#include <rte_ethdev.h>
> +#include <rte_malloc.h>
> +#include <rte_node_ip4_api.h>
> +#include <rte_node_ip6_api.h>
> +
> +#include "cli_priv.h"
> +#include "module_api.h"
> +
> +#define CMD_MAX_TOKENS 256
> +#define MAX_LINE_SIZE 2048
> +
> +static struct cli_node_head module_list = 
> STAILQ_HEAD_INITIALIZER(module_list);
> +
> +#define PARSE_DELIMITER " \f\n\r\t\v"
> +
> +static int
> +tokenize_string_parse(char *string, char *tokens[], uint32_t *n_tokens)
> +{
> +     uint32_t i;
> +
> +     if ((string == NULL) ||
> +             (tokens == NULL) ||
> +             (*n_tokens < 1))
> +             return -EINVAL;
> +
> +     for (i = 0; i < *n_tokens; i++) {
> +             tokens[i] = strtok_r(string, PARSE_DELIMITER, &string);
> +             if (tokens[i] == NULL)
> +                     break;
> +     }
> +
> +     if ((i == *n_tokens) && strtok_r(string, PARSE_DELIMITER, &string))
> +             return -E2BIG;
> +
> +     *n_tokens = i;
> +     return 0;
> +}
> +
> +static int
> +is_comment(char *in)
> +{
> +     if ((strlen(in) && index("!#%;", in[0])) ||
> +             (strncmp(in, "//", 2) == 0) ||
> +             (strncmp(in, "--", 2) == 0))
> +             return 1;
> +
> +     return 0;
> +}
> +
> +static bool
> +module_list_has_cmd_registered(const char *cmd)
> +{
> +     struct cli_node *node;
> +
> +     STAILQ_FOREACH(node, &module_list, next) {
> +             if (strcmp(node->cmd, cmd) == 0) {
> +                     rte_errno = EEXIST;
> +                     return 1;
> +             }
> +     }
> +     return 0;
> +}
> +
> +void
> +cli_module_register(const struct cli_module *module)
> +{
> +     struct cli_node *node;
> +
> +     /* Check sanity */
> +     if (module == NULL || module->process == NULL) {
> +             rte_errno = EINVAL;
> +             return;
> +     }
> +
> +     /* Check for duplicate name */
> +     if (module_list_has_cmd_registered(module->cmd)) {
> +             printf("module %s is already registered\n", module->cmd);
> +             return;
> +     }
> +
> +     node = malloc(sizeof(struct cli_node));
> +     if (node == NULL) {
> +             rte_errno = ENOMEM;
> +             return;
> +     }
> +
> +     /* Initialize the node */
> +     if (rte_strscpy(node->cmd, module->cmd, APP_CLI_CMD_NAME_SIZE) < 0) {
> +             free(node);
> +             return;
> +     }
> +     node->process = module->process;
> +     node->usage = module->usage;
> +
> +     /* Add the node at tail */
> +     STAILQ_INSERT_TAIL(&module_list, node, next);
> +}
> +
> +void
> +cli_process(char *in, char *out, size_t out_size, void *obj)
> +{
> +     char *tokens[CMD_MAX_TOKENS];
> +     struct cli_node *node;
> +     uint32_t n_tokens;
> +     int rc;
> +
> +     if (is_comment(in))
> +             return;
> +
> +     n_tokens = RTE_DIM(tokens);
> +     rc = tokenize_string_parse(in, tokens, &n_tokens);
> +     if (rc) {
> +             snprintf(out, out_size, MSG_ARG_TOO_MANY, "");
> +             return;
> +     }
> +
> +     if (n_tokens == 0)
> +             return;
> +
> +     if ((n_tokens == 1) && strcmp(tokens[0], "help") == 0) {
> +             STAILQ_FOREACH(node, &module_list, next) {
> +                     node->usage(tokens, n_tokens, out, out_size, obj);
> +             }
> +             return;
> +     }
> +
> +     if ((n_tokens >= 2) && strcmp(tokens[0], "help") == 0) {
> +             STAILQ_FOREACH(node, &module_list, next) {
> +                     if (strcmp(node->cmd, tokens[1]) == 0) {
> +                             node->usage(tokens, n_tokens, out, out_size, 
> obj);
> +                             return;
> +                     }
> +             }
> +             snprintf(out, out_size, MSG_CMD_UNKNOWN, tokens[0]);
> +             return;
> +     }
> +
> +     STAILQ_FOREACH(node, &module_list, next) {
> +             if (strcmp(node->cmd, tokens[0]) == 0) {
> +                     rc = node->process(tokens, n_tokens, out, out_size, 
> obj);
> +                     if (rc < 0)
> +                             snprintf(out, out_size, MSG_CMD_FAIL, 
> tokens[0]);
> +
> +                     return;
> +             }
> +     }
> +
> +     snprintf(out, out_size, MSG_CMD_UNKNOWN, tokens[0]);
> +}
> +
> +int
> +cli_script_process(const char *file_name, size_t msg_in_len_max, size_t
> msg_out_len_max, void *obj)
> +{
> +     char *msg_in = NULL, *msg_out = NULL;
> +     FILE *f = NULL;
> +
> +     /* Check input arguments */
> +     if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max 
> == 0) ||
> +         (msg_out_len_max == 0))
> +             return -EINVAL;
> +
> +     msg_in = malloc(msg_in_len_max + 1);
> +     msg_out = malloc(msg_out_len_max + 1);
> +     if ((msg_in == NULL) || (msg_out == NULL)) {
> +             free(msg_out);
> +             free(msg_in);
> +             return -ENOMEM;
> +     }
> +
> +     /* Open input file */
> +     f = fopen(file_name, "r");
> +     if (f == NULL) {
> +             free(msg_out);
> +             free(msg_in);
> +             return -EIO;
> +     }
> +
> +     /* Read file */
> +     while (1) {
> +             if (fgets(msg_in, msg_in_len_max + 1, f) == NULL)
> +                     break;
> +
> +             msg_out[0] = 0;
> +
> +             cli_process(msg_in, msg_out, msg_out_len_max, obj);
> +
> +             if (strlen(msg_out))
> +                     printf("%s", msg_out);
> +     }
> +
> +     /* Close file */
> +     fclose(f);
> +     free(msg_out);
> +     free(msg_in);
> +     return 0;
> +}
> diff --git a/app/graph/cli.h b/app/graph/cli.h
> new file mode 100644
> index 0000000000..2bd89f3d1f
> --- /dev/null
> +++ b/app/graph/cli.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CLI_H
> +#define APP_GRAPH_CLI_H
> +
> +/* Macros */
> +#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
> +#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
> +#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
> +#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
> +#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
> +#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
> +#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
> +#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
> +#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
> +#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
> +#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
> +
> +#define APP_CLI_CMD_NAME_SIZE        64
> +
> +/* Typedefs */
> +typedef int (*cli_module_t)(char **tokens, uint32_t n_tokens, char *out, 
> size_t out_size,
> +                          void *obj);
> +
> +/* Structures */
> +struct cli_module {
> +     char cmd[APP_CLI_CMD_NAME_SIZE]; /**< Name of the command to be
> registered. */
> +     cli_module_t process; /**< Command process function. */
> +     cli_module_t usage; /**< Help command process function. */
> +};
> +
> +/* APIs */
> +void cli_module_register(const struct cli_module *module);
> +
> +#define CLI_REGISTER(module)                 \
> +     RTE_INIT(cli_register_##module)         \
> +     {                                       \
> +             cli_module_register(&module);   \
> +     }
> +
> +void cli_process(char *in, char *out, size_t out_size, void *arg);
> +
> +int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t
> msg_out_len_max,
> +                    void *arg);
> +
> +#endif
> diff --git a/app/graph/cli_priv.h b/app/graph/cli_priv.h
> new file mode 100644
> index 0000000000..9ecc89c353
> --- /dev/null
> +++ b/app/graph/cli_priv.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CLI_PRIV_H
> +#define APP_GRAPH_CLI_PRIV_H
> +
> +#include "cli.h"
> +
> +struct cli_node {
> +     STAILQ_ENTRY(cli_node) next;     /**< Next node in the list. */
> +     char cmd[APP_CLI_CMD_NAME_SIZE]; /**< Name of the command. */
> +     cli_module_t process;            /**< Command process function. */
> +     cli_module_t usage;             /**< Help command process function. */
> +};
> +
> +STAILQ_HEAD(cli_node_head, cli_node);
> +
> +#endif
> diff --git a/app/graph/conn.c b/app/graph/conn.c
> new file mode 100644
> index 0000000000..dabc8deca2
> --- /dev/null
> +++ b/app/graph/conn.c
> @@ -0,0 +1,284 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <netinet/in.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/epoll.h>
> +#include <sys/socket.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +
> +#include "module_api.h"
> +
> +#define MSG_CMD_TOO_LONG "Command too long."
> +
> +static int
> +data_event_handle(struct conn *conn, int fd_client)
> +{
> +     ssize_t len, i, rc = 0;
> +
> +     /* Read input message */
> +     len = read(fd_client, conn->buf, conn->buf_size);
> +     if (len == -1) {
> +             if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
> +                     return 0;
> +
> +             return -1;
> +     }
> +
> +     if (len == 0)
> +             return rc;
> +
> +     /* Handle input messages */
> +     for (i = 0; i < len; i++) {
> +             if (conn->buf[i] == '\n') {
> +                     size_t n;
> +
> +                     conn->msg_in[conn->msg_in_len] = 0;
> +                     conn->msg_out[0] = 0;
> +
> +                     conn->msg_handle(conn->msg_in, conn->msg_out, conn-
> >msg_out_len_max,
> +                                      conn->msg_handle_arg);
> +
> +                     n = strlen(conn->msg_out);
> +                     if (n) {
> +                             rc = write(fd_client, conn->msg_out, n);
> +                             if (rc == -1)
> +                                     goto exit;
> +                     }
> +
> +                     conn->msg_in_len = 0;
> +             } else if (conn->msg_in_len < conn->msg_in_len_max) {
> +                     conn->msg_in[conn->msg_in_len] = conn->buf[i];
> +                     conn->msg_in_len++;
> +             } else {
> +                     rc = write(fd_client, MSG_CMD_TOO_LONG,
> strlen(MSG_CMD_TOO_LONG));
> +                     if (rc == -1)
> +                             goto exit;
> +
> +                     conn->msg_in_len = 0;
> +             }
> +     }
> +
> +     /* Write prompt */
> +     rc = write(fd_client, conn->prompt, strlen(conn->prompt));
> +     rc = (rc == -1) ? -1 : 0;
> +
> +exit:
> +     return rc;
> +}
> +
> +static int
> +control_event_handle(struct conn *conn, int fd_client)
> +{
> +     int rc;
> +
> +     rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
> +     if (rc == -1)
> +             goto exit;
> +
> +     rc = close(fd_client);
> +     if (rc == -1)
> +             goto exit;
> +
> +     rc = 0;
> +
> +exit:
> +     return rc;
> +}
> +
> +struct conn *
> +conn_init(struct conn_params *p)
> +{
> +     int fd_server, fd_client_group, rc;
> +     struct sockaddr_in server_address;
> +     struct conn *conn = NULL;
> +
> +     memset(&server_address, 0, sizeof(server_address));
> +
> +     /* Check input arguments */
> +     if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || 
> (p->addr ==
> NULL) ||
> +         (p->buf_size == 0) || (p->msg_in_len_max == 0) || 
> (p->msg_out_len_max == 0)
> ||
> +         (p->msg_handle == NULL))
> +             goto exit;
> +
> +     rc = inet_aton(p->addr, &server_address.sin_addr);
> +     if (rc == 0)
> +             goto exit;
> +
> +     /* Memory allocation */
> +     conn = calloc(1, sizeof(struct conn));
> +     if (conn == NULL)
> +             goto exit;
> +
> +     conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
> +     conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
> +     conn->buf = calloc(1, p->buf_size);
> +     conn->msg_in = calloc(1, p->msg_in_len_max + 1);
> +     conn->msg_out = calloc(1, p->msg_out_len_max + 1);
> +
> +     if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == 
> NULL)
> ||
> +         (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
> +             conn_free(conn);
> +             conn = NULL;
> +             goto exit;
> +     }
> +
> +     /* Server socket */
> +     server_address.sin_family = AF_INET;
> +     server_address.sin_port = htons(p->port);
> +
> +     fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
> +     if (fd_server == -1) {
> +             conn_free(conn);
> +             conn = NULL;
> +             goto exit;
> +     }
> +
> +     rc = bind(fd_server, (struct sockaddr *)&server_address, 
> sizeof(server_address));
> +     if (rc == -1) {
> +             conn_free(conn);
> +             close(fd_server);
> +             conn = NULL;
> +             goto exit;
> +     }
> +
> +     rc = listen(fd_server, 16);
> +     if (rc == -1) {
> +             conn_free(conn);
> +             close(fd_server);
> +             conn = NULL;
> +             goto exit;
> +     }
> +
> +     /* Client group */
> +     fd_client_group = epoll_create(1);
> +     if (fd_client_group == -1) {
> +             conn_free(conn);
> +             close(fd_server);
> +             conn = NULL;
> +             goto exit;
> +     }
> +
> +     /* Fill in */
> +     strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
> +     strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
> +     conn->buf_size = p->buf_size;
> +     conn->msg_in_len_max = p->msg_in_len_max;
> +     conn->msg_out_len_max = p->msg_out_len_max;
> +     conn->msg_in_len = 0;
> +     conn->fd_server = fd_server;
> +     conn->fd_client_group = fd_client_group;
> +     conn->msg_handle = p->msg_handle;
> +     conn->msg_handle_arg = p->msg_handle_arg;
> +
> +exit:
> +     return conn;
> +}
> +
> +void
> +conn_free(struct conn *conn)
> +{
> +     if (conn == NULL)
> +             return;
> +
> +     if (conn->fd_client_group)
> +             close(conn->fd_client_group);
> +
> +     if (conn->fd_server)
> +             close(conn->fd_server);
> +
> +     free(conn->msg_out);
> +     free(conn->msg_in);
> +     free(conn->prompt);
> +     free(conn->welcome);
> +     free(conn);
> +}
> +
> +int
> +conn_req_poll(struct conn *conn)
> +{
> +     struct sockaddr_in client_address;
> +     socklen_t client_address_length;
> +     struct epoll_event event;
> +     int fd_client, rc;
> +
> +     /* Check input arguments */
> +     if (conn == NULL)
> +             return -1;
> +
> +     /* Server socket */
> +     client_address_length = sizeof(client_address);
> +     fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
> +                         &client_address_length, SOCK_NONBLOCK);
> +     if (fd_client == -1) {
> +             if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
> +                     return 0;
> +
> +             return -1;
> +     }
> +
> +     /* Client group */
> +     event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
> +     event.data.fd = fd_client;
> +
> +     rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
> +     if (rc == -1) {
> +             close(fd_client);
> +             goto exit;
> +     }
> +
> +     /* Client */
> +     rc = write(fd_client, conn->welcome, strlen(conn->welcome));
> +     if (rc == -1) {
> +             close(fd_client);
> +             goto exit;
> +     }
> +
> +     rc = write(fd_client, conn->prompt, strlen(conn->prompt));
> +     if (rc == -1) {
> +             close(fd_client);
> +             goto exit;
> +     }
> +
> +     rc = 0;
> +
> +exit:
> +     return rc;
> +}
> +
> +int
> +conn_msg_poll(struct conn *conn)
> +{
> +     int fd_client, rc, rc_data = 0, rc_control = 0;
> +     struct epoll_event event;
> +
> +     /* Check input arguments */
> +     if (conn == NULL)
> +             return -1;
> +
> +     /* Client group */
> +     rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
> +     if ((rc == -1) || rc == 0)
> +             return rc;
> +
> +     fd_client = event.data.fd;
> +
> +     /* Data available */
> +     if (event.events & EPOLLIN)
> +             rc_data = data_event_handle(conn, fd_client);
> +
> +     /* Control events */
> +     if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
> +             rc_control = control_event_handle(conn, fd_client);
> +
> +     if (rc_data || rc_control)
> +             return -1;
> +
> +     return 0;
> +}
> diff --git a/app/graph/conn.h b/app/graph/conn.h
> new file mode 100644
> index 0000000000..770964cf4c
> --- /dev/null
> +++ b/app/graph/conn.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CONN_H
> +#define APP_GRAPH_CONN_H
> +
> +#define CONN_WELCOME_LEN_MAX 1024
> +#define CONN_PROMPT_LEN_MAX 16
> +
> +typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t
> msg_out_len_max, void *arg);
> +
> +struct conn {
> +     char *welcome;
> +     char *prompt;
> +     char *buf;
> +     char *msg_in;
> +     char *msg_out;
> +     size_t buf_size;
> +     size_t msg_in_len_max;
> +     size_t msg_out_len_max;
> +     size_t msg_in_len;
> +     int fd_server;
> +     int fd_client_group;
> +     conn_msg_handle_t msg_handle;
> +     void *msg_handle_arg;
> +};
> +
> +struct conn_params {
> +     const char *welcome;
> +     const char *prompt;
> +     const char *addr;
> +     uint16_t port;
> +     size_t buf_size;
> +     size_t msg_in_len_max;
> +     size_t msg_out_len_max;
> +     conn_msg_handle_t msg_handle;
> +     void *msg_handle_arg;
> +};
> +
> +struct conn *conn_init(struct conn_params *p);
> +void conn_free(struct conn *conn);
> +int conn_req_poll(struct conn *conn);
> +int conn_msg_poll(struct conn *conn);
> +
> +#endif
> diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
> new file mode 100644
> index 0000000000..840a8ca42f
> --- /dev/null
> +++ b/app/graph/ethdev.c
> @@ -0,0 +1,632 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_bitops.h>
> +#include <rte_ethdev.h>
> +#include <rte_mempool.h>
> +
> +#include "ethdev_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
> +
> +static const char
> +cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
> +
> +static const char
> +cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues>
> <mempool_name> "
> +                 "[mtu <mtu_sz>]";
> +static const char
> +cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
> +
> +static const char
> +cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask
> <mask>";
> +
> +static const char
> +cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask
> <mask>";
> +
> +static struct rte_eth_conf port_conf_default = {
> +     .link_speeds = 0,
> +     .rxmode = {
> +             .mq_mode = RTE_ETH_MQ_RX_NONE,
> +             .mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo
> frame MTU */
> +     },
> +     .rx_adv_conf = {
> +             .rss_conf = {
> +                     .rss_key = NULL,
> +                     .rss_key_len = 40,
> +                     .rss_hf = 0,
> +             },
> +     },
> +     .txmode = {
> +             .mq_mode = RTE_ETH_MQ_TX_NONE,
> +     },
> +     .lpbk_mode = 0,
> +};
> +
> +uint32_t enabled_port_mask;
> +struct ethdev port_list[RTE_MAX_ETHPORTS];
> +
> +void *
> +ethdev_mempool_list_by_portid(uint16_t portid)
> +{
> +     if (portid >= RTE_MAX_ETHPORTS)
> +             return NULL;
> +
> +     return &port_list[portid].config.rx.mp;
> +}
> +
> +int16_t
> +ethdev_portid_by_ip4(uint32_t ip)
> +{
> +     int portid = -EINVAL;
> +     int i;
> +
> +     for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
> +             if ((port_list[i].ip4_addr.ip & route4[i].netmask) == (ip &
> route4[i].netmask))
> +                     break;
> +     }
> +
> +     if (i == RTE_MAX_ETHPORTS)
> +             return portid;
> +
> +     return port_list[i].config.port_id;
> +}
> +
> +int16_t
> +ethdev_portid_by_ip6(uint8_t *ip)
> +{
> +     int portid = -EINVAL;
> +     int i, j;
> +
> +     for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
> +             for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
> +                     if ((port_list[i].ip6_addr.ip[j] & route6[i].mask[j]) !=
> +                         (ip[j] & route6[i].mask[j]))
> +                             break;
> +             }
> +
> +             if (j == ETHDEV_IPV6_ADDR_LEN)
> +                     break;
> +     }
> +
> +     if (i == RTE_MAX_ETHPORTS)
> +             return portid;
> +
> +     return port_list[i].config.port_id;
> +}
> +
> +void
> +ethdev_stop(void)
> +{
> +     uint16_t portid;
> +     int rc;
> +
> +     RTE_ETH_FOREACH_DEV(portid) {
> +             if ((enabled_port_mask & (1 << portid)) == 0)
> +                     continue;
> +             printf("Closing port %d...", portid);
> +             rc = rte_eth_dev_stop(portid);
> +             if (rc != 0)
> +                     printf("Failed to stop port %u: %s\n",
> +                                     portid, rte_strerror(-rc));
> +             rte_eth_dev_close(portid);
> +             printf(" Done\n");
> +     }
> +
> +     /* clean up the EAL */
> +     rte_eal_cleanup();
> +     printf("Bye...\n");
> +}
> +
> +void
> +ethdev_start(void)
> +{
> +     uint16_t portid;
> +     int rc;
> +
> +     RTE_ETH_FOREACH_DEV(portid)
> +     {
> +             if ((enabled_port_mask & (1 << portid)) == 0)
> +                     continue;
> +
> +             rc = rte_eth_dev_start(portid);
> +             if (rc < 0)
> +                     rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, 
> port=%d\n",
> rc, portid);
> +     }
> +}
> +
> +
> +static int
> +ethdev_show(const char *name, char **out, size_t *out_size)
> +{
> +     uint16_t mtu = 0, port_id = 0;
> +     struct rte_eth_dev_info info;
> +     struct rte_eth_stats stats;
> +     struct rte_ether_addr addr;
> +     struct rte_eth_link link;
> +     uint32_t length;
> +     int rc;
> +
> +     rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +     if (rc < 0)
> +             return rc;
> +
> +     rte_eth_dev_info_get(port_id, &info);
> +     rte_eth_stats_get(port_id, &stats);
> +     rte_eth_macaddr_get(port_id, &addr);
> +     rte_eth_link_get(port_id, &link);
> +     rte_eth_dev_get_mtu(port_id, &mtu);
> +
> +     snprintf(*out, *out_size,
> +              "%s: flags=<%s> mtu %u\n"
> +              "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
> +              "\tport# %u  speed %s\n"
> +              "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
> +              "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" 
> PRIu64"\n"
> +              "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
> +              "\tTX errors %" PRIu64"\n\n",
> +              name,
> +              link.link_status ? "UP" : "DOWN",
> +              mtu,
> +              RTE_ETHER_ADDR_BYTES(&addr),
> +              info.nb_rx_queues,
> +              info.nb_tx_queues,
> +              port_id,
> +              rte_eth_link_speed_to_str(link.link_speed),
> +              stats.ipackets,
> +              stats.ibytes,
> +              stats.ierrors,
> +              stats.imissed,
> +              stats.rx_nombuf,
> +              stats.opackets,
> +              stats.obytes,
> +              stats.oerrors);
> +
> +     length = strlen(*out);
> +     *out_size -= length;
> +     *out += length;
> +     return 0;
> +}
> +
> +static int
> +ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
> +{
> +     uint16_t portid = 0;
> +     int rc;
> +
> +     rc = rte_eth_dev_get_port_by_name(name, &portid);
> +     if (rc < 0)
> +             return rc;
> +
> +     port_list[portid].ip4_addr.ip = config->ip;
> +     port_list[portid].ip4_addr.mask = config->mask;
> +     return 0;
> +}
> +
> +static int
> +ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
> +{
> +     uint16_t portid = 0;
> +     int rc, i;
> +
> +     rc = rte_eth_dev_get_port_by_name(name, &portid);
> +     if (rc < 0)
> +             return rc;
> +
> +     for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
> +             port_list[portid].ip6_addr.ip[i] = config->ip[i];
> +             port_list[portid].ip6_addr.mask[i] = config->mask[i];
> +     }
> +
> +     return 0;
> +}
> +
> +static int
> +ethdev_prom_mode_config(const char *name, bool enable)
> +{
> +     uint16_t portid = 0;
> +     int rc;
> +
> +     rc = rte_eth_dev_get_port_by_name(name, &portid);
> +     if (rc < 0)
> +             return rc;
> +
> +     if (enable)
> +             rc = rte_eth_promiscuous_enable(portid);
> +     else
> +             rc = rte_eth_promiscuous_disable(portid);
> +
> +     if (rc < 0)
> +             return rc;
> +
> +     port_list[portid].config.promiscuous = enable;
> +     return 0;
> +}
> +
> +static int
> +ethdev_mtu_config(const char *name, uint32_t mtu)
> +{
> +     uint16_t portid = 0;
> +     int rc;
> +
> +     rc = rte_eth_dev_get_port_by_name(name, &portid);
> +     if (rc < 0)
> +             return rc;
> +
> +     rc = rte_eth_dev_set_mtu(portid, mtu);
> +     if (rc < 0)
> +             return rc;
> +
> +     port_list[portid].config.mtu = mtu;
> +     return 0;
> +}
> +
> +static int
> +ethdev_process(const char *name, struct ethdev_config *params)
> +{
> +     struct rte_eth_dev_info port_info;
> +     struct rte_eth_conf port_conf;
> +     struct ethdev_rss_config *rss;
> +     struct rte_mempool *mempool;
> +     struct rte_ether_addr smac;
> +     int numa_node, rc;
> +     uint16_t port_id = 0;
> +     uint32_t i;
> +
> +     /* Check input params */
> +     if (!name || !name[0] || !params || !params->rx.n_queues || !params-
> >rx.queue_size ||
> +         !params->tx.n_queues || !params->tx.queue_size)
> +             return -EINVAL;
> +
> +     rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +     if (rc)
> +             return -EINVAL;
> +
> +     rc = rte_eth_dev_info_get(port_id, &port_info);
> +     if (rc)
> +             return -EINVAL;
> +
> +     mempool = rte_mempool_lookup(params->rx.mempool_name);
> +     if (!mempool)
> +             return -EINVAL;
> +
> +     params->rx.mp = mempool;
> +
> +     rss = params->rx.rss;
> +     if (rss) {
> +             if (!port_info.reta_size || port_info.reta_size >
> RTE_ETH_RSS_RETA_SIZE_512)
> +                     return -EINVAL;
> +
> +             if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX)
> +                     return -EINVAL;
> +
> +             for (i = 0; i < rss->n_queues; i++)
> +                     if (rss->queue_id[i] >= port_info.max_rx_queues)
> +                             return -EINVAL;
> +     }
> +
> +     /* Port */
> +     memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
> +     if (rss) {
> +             uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP |
> RTE_ETH_RSS_UDP;
> +
> +             port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
> +             port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf &
> port_info.flow_type_rss_offloads;
> +     }
> +
> +     numa_node = rte_eth_dev_socket_id(port_id);
> +     if (numa_node == SOCKET_ID_ANY)
> +             numa_node = 0;
> +
> +     if (params->mtu)
> +             port_conf.rxmode.mtu = params->mtu;
> +
> +     rc = rte_eth_dev_configure(port_id, params->rx.n_queues, 
> params->tx.n_queues,
> +                                    &port_conf);
> +     if (rc < 0)
> +             return -EINVAL;
> +
> +     rc = rte_eth_macaddr_get(port_id, &smac);
> +     if (rc < 0)
> +             return -EINVAL;
> +
> +     printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
> +             smac.addr_bytes[0], smac.addr_bytes[1],
> +             smac.addr_bytes[2], smac.addr_bytes[3],
> +             smac.addr_bytes[4], smac.addr_bytes[5]);
> +
> +     /* Port RX */
> +     for (i = 0; i < params->rx.n_queues; i++) {
> +             rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size,
> numa_node, NULL,
> +                     mempool);
> +             if (rc < 0)
> +                     return -EINVAL;
> +     }
> +
> +     /* Port TX */
> +     for (i = 0; i < params->tx.n_queues; i++) {
> +             rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size,
> numa_node, NULL);
> +             if (rc < 0)
> +                     return -EINVAL;
> +     }
> +
> +     memcpy(&port_list[port_id].config, params, sizeof(struct 
> ethdev_config));
> +     memcpy(port_list[port_id].config.dev_name, name, strlen(name));
> +     port_list[port_id].config.port_id = port_id;
> +     enabled_port_mask |= RTE_BIT32(port_id);
> +     return 0;
> +}
> +
> +static int
> +cmd_ethdev_mtu(char **tokens, uint32_t n_tokens __rte_unused, char *out, 
> size_t
> out_size,
> +            void *obj __rte_unused)
> +{
> +     int rc = -EINVAL;
> +     uint32_t mtu = 0;
> +
> +     if (n_tokens != 4) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             return rc;
> +     }
> +
> +     if (parser_uint32_read(&mtu, tokens[3]) != 0) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "mtu_sz");
> +             return rc;
> +     }
> +
> +     rc = ethdev_mtu_config(tokens[1], mtu);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +     return rc;
> +}
> +
> +static int
> +cmd_ethdev_prom_mode(char **tokens, uint32_t n_tokens __rte_unused, char 
> *out,
> size_t out_size,
> +                  void *obj __rte_unused)
> +{
> +     bool enable = false;
> +     int rc = -EINVAL;
> +
> +     if (n_tokens != 4) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             return rc;
> +     }
> +
> +     if (strcmp(tokens[3], "on") == 0)
> +             enable = true;
> +
> +     rc = ethdev_prom_mode_config(tokens[1], enable);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +     return rc;
> +}
> +
> +static int
> +cmd_ip4_addr(char **tokens, uint32_t n_tokens, char *out, size_t out_size, 
> void *obj
> __rte_unused)
> +{
> +     struct ipv4_addr_config config;
> +     int rc = -EINVAL;
> +
> +     if (n_tokens != 8) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[3], "addr")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "addr");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[4], "add")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> +             goto exit;
> +     }
> +
> +     if (parser_ip4_read(&config.ip, tokens[5])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[6], "netmask")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> +             goto exit;
> +     }
> +
> +     if (parser_ip4_read(&config.mask, tokens[7])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> +             goto exit;
> +     }
> +
> +     rc = ethdev_ip4_addr_add(tokens[1], &config);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
> +
> +exit:
> +     return rc;
> +}
> +
> +static int
> +cmd_ip6_addr(char **tokens, uint32_t n_tokens, char *out, size_t out_size, 
> void *obj
> __rte_unused)
> +{
> +     struct ipv6_addr_config config;
> +     int rc = -EINVAL;
> +
> +     if (n_tokens != 8) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[3], "addr")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "addr");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[4], "add")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> +             goto exit;
> +     }
> +
> +     if (parser_ip6_read(config.ip, tokens[5])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[6], "netmask")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> +             goto exit;
> +     }
> +
> +     if (parser_ip6_read(config.mask, tokens[7])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> +             goto exit;
> +     }
> +
> +     rc = ethdev_ip6_addr_add(tokens[1], &config);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
> +
> +exit:
> +     return rc;
> +}
> +
> +static int
> +cmd_ethdev_show(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> +     void *obj __rte_unused)
> +{
> +     int rc = -EINVAL;
> +
> +     if (n_tokens != 3) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             return rc;
> +     }
> +
> +     rc = ethdev_show(tokens[1], &out, &out_size);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
> +
> +     return rc;
> +}
> +
> +static int
> +cmd_ethdev(char **tokens, uint32_t n_tokens, char *out, size_t out_size, 
> void *obj
> __rte_unused)
> +{
> +     struct ethdev_config config;
> +     char *name;
> +     int rc;
> +
> +     if (n_tokens < 7) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             return -EINVAL;
> +     }
> +
> +     memset(&config, 0, sizeof(struct ethdev_config));
> +     name = tokens[1];
> +
> +     if (strcmp(tokens[2], "rxq") != 0) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "rxq");
> +             return -EINVAL;
> +     }
> +
> +     if (parser_uint32_read(&config.rx.n_queues, tokens[3]) != 0) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "n_queues");
> +             return -EINVAL;
> +     }
> +
> +     if (strcmp(tokens[4], "txq") != 0) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "txq");
> +             return -EINVAL;
> +     }
> +
> +     if (parser_uint32_read(&config.tx.n_queues, tokens[5]) != 0) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "n_queues");
> +             return -EINVAL;
> +     }
> +
> +     mempcpy(config.rx.mempool_name, tokens[6], strlen(tokens[6]));
> +
> +     if (n_tokens > 7) {
> +             if (strcmp(tokens[7], "mtu") != 0) {
> +                     snprintf(out, out_size, MSG_ARG_NOT_FOUND, "mtu");
> +                     return -EINVAL;
> +             }
> +
> +             if (parser_uint32_read(&config.mtu, tokens[8]) != 0) {
> +                     snprintf(out, out_size, MSG_ARG_INVALID, "mtu_sz");
> +                     return -EINVAL;
> +             }
> +     }
> +
> +     config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
> +     config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
> +
> +     rc = ethdev_process(name, &config);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +     return rc;
> +}
> +
> +static int
> +cli_ethdev_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, 
> char
> *out,
> +                size_t out_size, void *obj __rte_unused)
> +{
> +     size_t len;
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "\n%s\n",
> +              "----------------------------- ethdev command help 
> -----------------------------");
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_ethdev_help);
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_ethdev_ip4_addr_help);
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_ethdev_ip6_addr_help);
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_ethdev_prom_mode_help);
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_ethdev_mtu_help);
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_ethdev_show_help);
> +
> +     return 0;
> +}
> +
> +static int
> +cli_ethdev(char **tokens, uint32_t n_tokens, char *out, size_t out_size, 
> void *obj)
> +{
> +     if (strcmp(tokens[2], "show") == 0)
> +             return cmd_ethdev_show(tokens, n_tokens, out, out_size, obj);
> +     else if (strcmp(tokens[2], "mtu") == 0)
> +             return cmd_ethdev_mtu(tokens, n_tokens, out, out_size, obj);
> +     else if (strcmp(tokens[2], "promiscuous") == 0)
> +             return cmd_ethdev_prom_mode(tokens, n_tokens, out, out_size, 
> obj);
> +     else if (strcmp(tokens[2], "ip4") == 0)
> +             return cmd_ip4_addr(tokens, n_tokens, out, out_size, obj);
> +     else if (strcmp(tokens[2], "ip6") == 0)
> +             return cmd_ip6_addr(tokens, n_tokens, out, out_size, obj);
> +     else
> +             return cmd_ethdev(tokens, n_tokens, out, out_size, obj);
> +}
> +
> +static struct cli_module ethdev = {
> +     .cmd = "ethdev",
> +     .process = cli_ethdev,
> +     .usage = cli_ethdev_help,
> +};
> +
> +CLI_REGISTER(ethdev);
> diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
> new file mode 100644
> index 0000000000..9c3de49826
> --- /dev/null
> +++ b/app/graph/ethdev.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_H
> +#define APP_GRAPH_ETHDEV_H
> +
> +#define ETHDEV_IPV6_ADDR_LEN 16
> +
> +struct ipv4_addr_config {
> +     uint32_t ip;
> +     uint32_t mask;
> +};
> +
> +struct ipv6_addr_config {
> +     uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
> +     uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
> +};
> +
> +extern uint32_t enabled_port_mask;
> +
> +void ethdev_start(void);
> +void ethdev_stop(void);
> +void *ethdev_mempool_list_by_portid(uint16_t portid);
> +int16_t ethdev_portid_by_ip4(uint32_t ip);
> +int16_t ethdev_portid_by_ip6(uint8_t *ip);
> +
> +#endif
> diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
> new file mode 100644
> index 0000000000..1026c2e5b6
> --- /dev/null
> +++ b/app/graph/ethdev_priv.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_PRIV_H
> +#define APP_GRAPH_ETHDEV_PRIV_H
> +
> +#include "ethdev.h"
> +
> +#define ETHDEV_RXQ_RSS_MAX   16
> +#define ETHDEV_RX_DESC_DEFAULT 1024
> +#define ETHDEV_TX_DESC_DEFAULT 1024
> +
> +struct ethdev_rss_config {
> +     uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
> +     uint32_t n_queues;
> +};
> +
> +struct ethdev_config {
> +     char dev_name[RTE_ETH_NAME_MAX_LEN];
> +     uint16_t port_id;
> +
> +     struct {
> +             uint32_t n_queues;
> +             uint32_t queue_size;
> +             char mempool_name[RTE_MEMPOOL_NAMESIZE];
> +             struct rte_mempool *mp;
> +             struct ethdev_rss_config *rss;
> +     } rx;
> +
> +     struct {
> +             uint32_t n_queues;
> +             uint32_t queue_size;
> +     } tx;
> +
> +     int promiscuous;
> +     uint32_t mtu;
> +};
> +
> +struct ethdev {
> +     struct ethdev_config config;
> +     struct ipv4_addr_config ip4_addr;
> +     struct ipv6_addr_config ip6_addr;
> +};
> +
> +#endif
> diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
> new file mode 100644
> index 0000000000..d706d145c1
> --- /dev/null
> +++ b/app/graph/ethdev_rx.c
> @@ -0,0 +1,139 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_ethdev.h>
> +
> +#include "ethdev_rx_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core
> <core_id>";
> +
> +static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
> +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +struct lcore_params *lcore_params = lcore_params_array;
> +struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +uint16_t nb_lcore_params;
> +
> +static void
> +rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
> +{
> +     uint8_t n_rx_queue;
> +
> +     n_rx_queue = lcore_conf[core].n_rx_queue;
> +     lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
> +     lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
> +     lcore_conf[core].n_rx_queue++;
> +}
> +
> +uint8_t
> +ethdev_rx_num_rx_queues_get(uint16_t port)
> +{
> +     int queue = -1;
> +     uint16_t i;
> +
> +     for (i = 0; i < nb_lcore_params; ++i) {
> +             if (lcore_params[i].port_id == port) {
> +                     if (lcore_params[i].queue_id == queue + 1)
> +                             queue = lcore_params[i].queue_id;
> +                     else
> +                             rte_exit(EXIT_FAILURE,
> +                                      "Queue ids of the port %d must be"
> +                                      " in sequence and must start with 0\n",
> +                                      lcore_params[i].port_id);
> +             }
> +     }
> +
> +     return (uint8_t)(++queue);
> +}
> +
> +static int
> +ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
> +{
> +     uint16_t port_id;
> +     int rc;
> +
> +     if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
> +             return -EINVAL;
> +
> +     rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +     if (rc)
> +             return -EINVAL;
> +
> +     rx_map_configure(port_id, queue, core);
> +
> +     lcore_params_array[nb_lcore_params].port_id = port_id;
> +     lcore_params_array[nb_lcore_params].queue_id = queue;
> +     lcore_params_array[nb_lcore_params].lcore_id = core;
> +     nb_lcore_params++;
> +     return 0;
> +}
> +
> +static int
> +cli_ethdev_rx_help(char **tokens __rte_unused, uint32_t n_tokens 
> __rte_unused, char
> *out,
> +                size_t out_size, void *obj __rte_unused)
> +{
> +     size_t len;
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "\n%s\n",
> +              "---------------------------- ethdev_rx command help 
> ---------------------------");
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_ethdev_rx_help);
> +     return 0;
> +}
> +
> +static int
> +cli_ethdev_rx(char **tokens, uint32_t n_tokens, char *out, size_t out_size, 
> void *obj
> __rte_unused)
> +{
> +     char name[RTE_ETH_NAME_MAX_LEN];
> +     uint32_t core_id, queue;
> +     int rc = -EINVAL;
> +
> +     if (n_tokens != 8) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             goto exit;
> +     }
> +
> +     strcpy(name, tokens[3]);
> +
> +     if (strcmp(tokens[4], "queue") != 0) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "rxq");
> +             goto exit;
> +     }
> +
> +     if (parser_uint32_read(&queue, tokens[5]) != 0) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "queue");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[6], "core") != 0) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "core_id");
> +             goto exit;
> +     }
> +
> +     if (parser_uint32_read(&core_id, tokens[7]) != 0) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "queue");
> +             goto exit;
> +     }
> +
> +     rc = ethdev_rx_map_add(name, queue, core_id);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +     return rc;
> +}
> +
> +static struct cli_module ethdev_rx = {
> +     .cmd = "ethdev_rx",
> +     .process = cli_ethdev_rx,
> +     .usage = cli_ethdev_rx_help,
> +};
> +
> +CLI_REGISTER(ethdev_rx);
> diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
> new file mode 100644
> index 0000000000..d2c18f545f
> --- /dev/null
> +++ b/app/graph/ethdev_rx.h
> @@ -0,0 +1,32 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_RX_H
> +#define APP_GRAPH_ETHDEV_RX_H
> +
> +#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
> +#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
> +
> +struct lcore_rx_queue {
> +     uint16_t port_id;
> +     uint8_t queue_id;
> +     char node_name[RTE_NODE_NAMESIZE];
> +};
> +
> +struct lcore_conf {
> +     uint16_t n_rx_queue;
> +     struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
> +     struct rte_graph *graph;
> +     char name[RTE_GRAPH_NAMESIZE];
> +     rte_graph_t graph_id;
> +} __rte_cache_aligned;
> +
> +uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
> +
> +extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +extern struct lcore_params *lcore_params;
> +extern uint16_t nb_lcore_params;
> +
> +#endif
> diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
> new file mode 100644
> index 0000000000..d714f83739
> --- /dev/null
> +++ b/app/graph/ethdev_rx_priv.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
> +#define APP_GRAPH_ETHDEV_RX_PRIV_H
> +
> +#include <stdint.h>
> +
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#define MAX_RX_QUEUE_PER_PORT 128
> +#define MAX_JUMBO_PKT_LEN  9600
> +#define NB_SOCKETS 8
> +
> +struct lcore_params {
> +     uint16_t port_id;
> +     uint8_t queue_id;
> +     uint8_t lcore_id;
> +} __rte_cache_aligned;
> +
> +#endif
> diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
> new file mode 100644
> index 0000000000..9986e1b73e
> --- /dev/null
> +++ b/app/graph/examples/l3fwd.cli
> @@ -0,0 +1,87 @@
> +; SPDX-License-Identifier: BSD-3-Clause
> +; Copyright(c) 2023 Marvell.
> +
> +;
> +; Graph configuration for given usecase
> +;
> +graph l3fwd coremask ff model default
> +
> +;
> +; Mempools to be attached with ethdev
> +;
> +mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
> +
> +;
> +; DPDK devices and configuration.
> +;
> +; Note: Customize the parameters below to match your setup.
> +;
> +ethdev 0002:04:00.0 rxq 1 txq 8 mempool0 mtu 1500
> +ethdev 0002:05:00.0 rxq 1 txq 8 mempool0 mtu 1600
> +ethdev 0002:06:00.0 rxq 1 txq 8 mempool0 mtu 1500
> +ethdev 0002:07:00.0 rxq 1 txq 8 mempool0 mtu 1600
> +ethdev 0002:04:00.0 mtu 1700
> +ethdev 0002:05:00.0 promiscuous on
> +
> +;
> +; IPv4 addresses assigned to DPDK devices
> +;
> +ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
> +ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
> +ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
> +ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
> +
> +;
> +; IPv6 addresses assigned to DPDK devices
> +;
> +ethdev 0002:04:00.0 ip6 addr add 
> 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +ethdev 0002:05:00.0 ip6 addr add 
> 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +ethdev 0002:06:00.0 ip6 addr add 
> 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +ethdev 0002:07:00.0 ip6 addr add 
> 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +
> +;
> +; IPv4 routes which are installed to ipv4_lookup node for LPM processing
> +;
> +ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
> +ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
> +ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
> +ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
> +
> +;
> +; IPv6 routes which are installed to ipv6_lookup node for LPM processing
> +;
> +ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 
> netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
> +ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 
> netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
> +ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 
> netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
> +ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 
> netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
> +
> +;
> +; Peer MAC and IPv4 address mapping
> +;
> +neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
> +neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
> +neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
> +neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
> +
> +;
> +; Peer MAC and IPv6 address mapping
> +;
> +neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 
> 52:20:DA:4F:68:70
> +neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 
> 62:20:DA:4F:68:70
> +neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 
> 72:20:DA:4F:68:70
> +neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 
> 82:20:DA:4F:68:70
> +
> +;
> +; Port-Queue-Core mapping for ethdev_rx node
> +;
> +ethdev_rx map port 0002:04:00.0 queue 0 core 1
> +ethdev_rx map port 0002:05:00.0 queue 0 core 2
> +ethdev_rx map port 0002:06:00.0 queue 0 core 3
> +ethdev_rx map port 0002:07:00.0 queue 0 core 4
> +
> +;
> +; Graph start command to create graph.
> +;
> +; Note: No more command should come after this.
> +;
> +graph start
> diff --git a/app/graph/graph.c b/app/graph/graph.c
> new file mode 100644
> index 0000000000..8c75574ecd
> --- /dev/null
> +++ b/app/graph/graph.c
> @@ -0,0 +1,383 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_graph_worker.h>
> +#include <rte_log.h>
> +
> +#include "graph_priv.h"
> +#include "module_api.h"
> +
> +#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
> +
> +static const char
> +cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
> +                "model <rtc | mcd | default>";
> +
> +static const char * const supported_usecases[] = {"l3fwd"};
> +struct graph_config graph_config;
> +
> +/* Check the link rc of all ports in up to 9s, and print them finally */
> +static void
> +check_all_ports_link_status(uint32_t port_mask)
> +{
> +#define CHECK_INTERVAL 100 /* 100ms */
> +#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
> +     char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
> +     uint8_t count, all_ports_up, print_flag = 0;
> +     struct rte_eth_link link;
> +     uint16_t portid;
> +     int rc;
> +
> +     printf("\nChecking link rc");
> +     fflush(stdout);
> +     for (count = 0; count <= MAX_CHECK_TIME; count++) {
> +             if (force_quit)
> +                     return;
> +
> +             all_ports_up = 1;
> +             RTE_ETH_FOREACH_DEV(portid)
> +             {
> +                     if (force_quit)
> +                             return;
> +
> +                     if ((port_mask & (1 << portid)) == 0)
> +                             continue;
> +
> +                     memset(&link, 0, sizeof(link));
> +                     rc = rte_eth_link_get_nowait(portid, &link);
> +                     if (rc < 0) {
> +                             all_ports_up = 0;
> +                             if (print_flag == 1)
> +                                     printf("Port %u link get failed: %s\n",
> +                                            portid, rte_strerror(-rc));
> +                             continue;
> +                     }
> +
> +                     /* Print link rc if flag set */
> +                     if (print_flag == 1) {
> +                             rte_eth_link_to_str(link_rc_text, 
> sizeof(link_rc_text),
> +                                     &link);
> +                             printf("Port %d %s\n", portid, link_rc_text);
> +                             continue;
> +                     }
> +
> +                     /* Clear all_ports_up flag if any link down */
> +                     if (link.link_status == RTE_ETH_LINK_DOWN) {
> +                             all_ports_up = 0;
> +                             break;
> +                     }
> +             }
> +
> +             /* After finally printing all link rc, get out */
> +             if (print_flag == 1)
> +                     break;
> +
> +             if (all_ports_up == 0) {
> +                     printf(".");
> +                     fflush(stdout);
> +                     rte_delay_ms(CHECK_INTERVAL);
> +             }
> +
> +             /* Set the print_flag if all ports up or timeout */
> +             if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> +                     print_flag = 1;
> +                     printf("Done\n");
> +             }
> +     }
> +}
> +
> +static bool
> +parser_usecases_read(char *usecases)
> +{
> +     bool valid = false;
> +     uint32_t i, j = 0;
> +     char *token;
> +
> +     token = strtok(usecases, ",");
> +     while (token != NULL) {
> +             for (i = 0; i < RTE_DIM(supported_usecases); i++) {
> +                     if (strcmp(supported_usecases[i], token) == 0) {
> +                             graph_config.usecases[j].enabled = true;
> +                             strcpy(graph_config.usecases[j].name, token);
> +                             valid = true;
> +                             j++;
> +                             break;
> +                     }
> +             }
> +             token = strtok(NULL, ",");
> +     }
> +
> +     return valid;
> +}
> +
> +static uint64_t
> +graph_worker_count_get(void)
> +{
> +     uint64_t nb_worker = 0;
> +     uint64_t coremask;
> +
> +     coremask = graph_config.params.coremask;
> +     while (coremask) {
> +             if (coremask & 0x1)
> +                     nb_worker++;
> +
> +             coremask = (coremask >> 1);
> +     }
> +
> +     return nb_worker;
> +}
> +
> +static struct rte_node_ethdev_config *
> +graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
> +{
> +     uint32_t n_tx_queue, nb_conf = 0, lcore_id;
> +     uint16_t queueid, portid, nb_graphs = 0;
> +     uint8_t nb_rx_queue, queue;
> +     struct lcore_conf *qconf;
> +
> +     n_tx_queue = graph_worker_count_get();
> +     if (n_tx_queue > RTE_MAX_ETHPORTS)
> +             n_tx_queue = RTE_MAX_ETHPORTS;
> +
> +     RTE_ETH_FOREACH_DEV(portid) {
> +             /* Skip ports that are not enabled */
> +             if ((enabled_port_mask & (1 << portid)) == 0) {
> +                     printf("\nSkipping disabled port %d\n", portid);
> +                     continue;
> +             }
> +
> +             nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
> +
> +             /* Setup ethdev node config */
> +             ethdev_conf[nb_conf].port_id = portid;
> +             ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> +             ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
> +             ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
> +             ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
> +
> +             nb_conf++;
> +     }
> +
> +     for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +             if (rte_lcore_is_enabled(lcore_id) == 0)
> +                     continue;
> +
> +             qconf = &lcore_conf[lcore_id];
> +             printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> +             fflush(stdout);
> +
> +             /* Init RX queues */
> +             for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +                     portid = qconf->rx_queue_list[queue].port_id;
> +                     queueid = qconf->rx_queue_list[queue].queue_id;
> +
> +                     /* Add this queue node to its graph */
> +                     snprintf(qconf->rx_queue_list[queue].node_name,
> RTE_NODE_NAMESIZE,
> +                              "ethdev_rx-%u-%u", portid, queueid);
> +             }
> +             if (qconf->n_rx_queue)
> +                     nb_graphs++;
> +     }
> +
> +     printf("\n");
> +
> +     ethdev_start();
> +     check_all_ports_link_status(enabled_port_mask);
> +
> +     *num_conf = nb_conf;
> +     *num_graphs = nb_graphs;
> +     return ethdev_conf;
> +}
> +
> +static int
> +graph_start(void)
> +{
> +     struct rte_node_ethdev_config *conf;
> +     uint32_t nb_graphs = 0, nb_conf, i;
> +
> +     conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
> +     for (i = 0; i < MAX_GRAPH_USECASES; i++) {
> +             if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
> +                     if (graph_config.usecases[i].enabled) {
> +                             usecase_l3fwd_configure(conf, nb_conf, 
> nb_graphs);
> +                             break;
> +                     }
> +             }
> +     }
> +     return 0;
> +}
> +
> +static int
> +graph_config_add(char *usecases, struct graph_config *config)
> +{
> +     if (!parser_usecases_read(usecases))
> +             return -EINVAL;
> +
> +     graph_config.params.bsz = config->params.bsz;
> +     graph_config.params.tmo = config->params.tmo;
> +     graph_config.params.coremask = config->params.coremask;
> +     graph_config.model = config->model;
> +
> +     return 0;
> +}
> +
> +int
> +graph_walk_start(void *conf)
> +{
> +     struct lcore_conf *qconf;
> +     struct rte_graph *graph;
> +     uint32_t lcore_id;
> +
> +     RTE_SET_USED(conf);
> +
> +     lcore_id = rte_lcore_id();
> +     qconf = &lcore_conf[lcore_id];
> +     graph = qconf->graph;
> +
> +     if (!graph) {
> +             RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", 
> lcore_id);
> +             return 0;
> +     }
> +
> +     RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph 
> %s(%p)\n",
> lcore_id,
> +             qconf->name, graph);
> +
> +     while (likely(!force_quit))
> +             rte_graph_walk(graph);
> +
> +     return 0;
> +}
> +
> +void
> +graph_stats_print(void)
> +{
> +     const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
> +     const char clr[] = {27, '[', '2', 'J', '\0'};
> +     struct rte_graph_cluster_stats_param s_param;
> +     struct rte_graph_cluster_stats *stats;
> +     const char *pattern = "worker_*";
> +
> +     /* Prepare stats object */
> +     memset(&s_param, 0, sizeof(s_param));
> +     s_param.f = stdout;
> +     s_param.socket_id = SOCKET_ID_ANY;
> +     s_param.graph_patterns = &pattern;
> +     s_param.nb_graph_patterns = 1;
> +
> +     stats = rte_graph_cluster_stats_create(&s_param);
> +     if (stats == NULL)
> +             rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
> +
> +     while (!force_quit) {
> +             /* Clear screen and move to top left */
> +             printf("%s%s", clr, topLeft);
> +             rte_graph_cluster_stats_get(stats, 0);
> +             rte_delay_ms(1E3);
> +     }
> +
> +     rte_graph_cluster_stats_destroy(stats);
> +}
> +
> +static void
> +graph_config_process(char **tokens, uint32_t n_tokens, char *out, size_t 
> out_size,
> +                  void *obj __rte_unused)
> +{
> +     uint32_t bsz = 32, tmo = 0, coremask = 0xf;
> +     struct graph_config config;
> +     int idx = 2, rc;
> +     uint8_t model;
> +
> +     if (n_tokens < 4) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             return;
> +     }
> +
> +next_arg:
> +     if (strcmp(tokens[idx], "model")) {
> +             if (strcmp(tokens[idx], "bsz") == 0) {
> +                     if (parser_uint32_read(&bsz, tokens[idx + 1])) {
> +                             snprintf(out, out_size, MSG_ARG_INVALID, "bsz");
> +                             return;
> +                     }
> +
> +             } else if (strcmp(tokens[idx], "tmo") == 0) {
> +                     if (parser_uint32_read(&tmo, tokens[idx + 1])) {
> +                             snprintf(out, out_size, MSG_ARG_INVALID, "tmo");
> +                             return;
> +                     }
> +             } else if (strcmp(tokens[idx], "coremask") == 0) {
> +                     coremask = strtol(tokens[idx + 1], NULL, 16);
> +                     if (coremask == 0) {
> +                             snprintf(out, out_size, MSG_ARG_INVALID, "tmo");
> +                             return;
> +                     }
> +             } else {
> +                     snprintf(out, out_size, MSG_ARG_NOT_FOUND, "usecases
> params");
> +                     return;
> +             }
> +
> +             idx += 2;
> +             goto next_arg;
> +     } else {
> +             if (strcmp(tokens[idx + 1], "default") == 0) {
> +                     model = GRAPH_MODEL_RTC;
> +             } else if (strcmp(tokens[idx + 1], "rtc") == 0) {
> +                     model = GRAPH_MODEL_RTC;
> +             } else if (strcmp(tokens[idx + 1], "mcd") == 0) {
> +                     model = GRAPH_MODEL_MCD;
> +             } else {
> +                     snprintf(out, out_size, MSG_ARG_NOT_FOUND, "model
> arguments");
> +                     return;
> +             }
> +     }
> +
> +     config.params.bsz = bsz;
> +     config.params.tmo = tmo;
> +     config.params.coremask = coremask;
> +     config.model = model;
> +     rc = graph_config_add(tokens[1], &config);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +}
> +
> +static int
> +cli_graph_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, 
> char
> *out,
> +            size_t out_size, void *obj __rte_unused)
> +{
> +     size_t len;
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "\n%s\n",
> +              "----------------------------- graph command help 
> -----------------------------");
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_graph_help);
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", "graph start");
> +     return 0;
> +}
> +
> +static int
> +cli_graph(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void 
> *obj
> __rte_unused)
> +{
> +     if (strcmp(tokens[1], "start") == 0)
> +             graph_start();
> +     else
> +             graph_config_process(tokens, n_tokens, out, out_size, obj);
> +
> +     return 0;
> +}
> +
> +static struct cli_module graph = {
> +     .cmd = "graph",
> +     .process = cli_graph,
> +     .usage = cli_graph_help,
> +};
> +
> +CLI_REGISTER(graph);
> diff --git a/app/graph/graph.h b/app/graph/graph.h
> new file mode 100644
> index 0000000000..126e967d75
> --- /dev/null
> +++ b/app/graph/graph.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_H
> +#define APP_GRAPH_H
> +
> +int graph_walk_start(void *conf);
> +void graph_stats_print(void);
> +
> +#endif
> diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
> new file mode 100644
> index 0000000000..655a028fb2
> --- /dev/null
> +++ b/app/graph/graph_priv.h
> @@ -0,0 +1,32 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_PRIV_H
> +#define APP_GRAPH_PRIV_H
> +
> +#define MAX_GRAPH_USECASES 32
> +
> +enum graph_model {
> +     GRAPH_MODEL_RTC = 0x01,
> +     GRAPH_MODEL_MCD = 0x02,
> +};
> +
> +struct usecases {
> +     char name[32];
> +     bool enabled;
> +};
> +
> +struct usecase_params {
> +     uint64_t coremask;
> +     uint32_t bsz;
> +     uint32_t tmo;
> +};
> +
> +struct graph_config {
> +     struct usecases usecases[MAX_GRAPH_USECASES];
> +     struct usecase_params params;
> +     enum graph_model model;
> +};
> +
> +#endif
> diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
> new file mode 100644
> index 0000000000..5aba5b38f2
> --- /dev/null
> +++ b/app/graph/ip4_route.c
> @@ -0,0 +1,146 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_node_ip4_api.h>
> +
> +#include "module_api.h"
> +
> +static const char
> +cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via 
> <ip>";
> +
> +struct ipv4_route_config route4[MAX_ROUTE_ENTRIES];
> +
> +static uint8_t
> +convert_netmask_to_depth(uint32_t netmask)
> +{
> +     uint8_t zerobits = 0;
> +
> +     while ((netmask & 0x1) == 0) {
> +             netmask = netmask >> 1;
> +             zerobits++;
> +     }
> +
> +     return (32 - zerobits);
> +}
> +
> +static int
> +route_ip4_add(struct ipv4_route_config *route)
> +{
> +     int i;
> +
> +     for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
> +             if (!route4[i].is_used)
> +                     break;
> +     }
> +
> +     if (i == MAX_ROUTE_ENTRIES)
> +             return -ENOMEM;

[Nithin] Change neigh and route database to dynamic linked list instead of 
using static array.

> +
> +     route4[i].ip = route->ip;
> +     route4[i].netmask = route->netmask;
> +     route4[i].via = route->via;
> +     route4[i].is_used = true;
> +     return 0;
> +}
> +
> +int
> +route_ip4_add_to_lookup(void)
> +{
> +     struct ipv4_route_config *route = NULL;
> +     int rc = -EINVAL;
> +     uint8_t depth;
> +     int portid, i;
> +
> +     for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
> +             if (route4[i].is_used)
> +                     route = &route4[i];
> +
> +             portid = ethdev_portid_by_ip4(route->via);
> +             if (portid < 0) {
> +                     printf("Invalid portid found to install the route\n");
> +                     return rc;
> +             }
> +
> +             depth = convert_netmask_to_depth(route->netmask);
> +
> +             rc = rte_node_ip4_route_add(route->ip, depth, portid,
> +                                          RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
> +             if (rc < 0)
> +                     return rc;
> +     }
> +
> +     return 0;
> +}
> +
> +static int
> +cli_ipv4_lookup_help(char **tokens __rte_unused, uint32_t n_tokens 
> __rte_unused,
> char *out,
> +                  size_t out_size, void *obj __rte_unused)
> +{
> +     size_t len;
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "\n%s\n",
> +              "--------------------------- ipv4_lookup command help 
> --------------------------
> ");
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_ipv4_lookup_help);
> +     return 0;
> +}
> +
> +static int
> +cli_ipv4_lookup(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> +             void *obj __rte_unused)
> +{
> +     struct ipv4_route_config config;
> +     int rc = -EINVAL;
> +
> +     if (n_tokens != 9) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             goto exit;
> +     }
> +
> +     if (parser_ip4_read(&config.ip, tokens[4])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "ipv4");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[5], "netmask")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> +             goto exit;
> +     }
> +
> +     if (parser_ip4_read(&config.netmask, tokens[6])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[7], "via")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "via");
> +             goto exit;
> +     }
> +
> +     if (parser_ip4_read(&config.via, tokens[8])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "via ip");
> +             goto exit;
> +     }
> +
> +     rc = route_ip4_add(&config);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +     return rc;
> +}
> +
> +static struct cli_module ipv4_lookup = {
> +     .cmd = "ipv4_lookup",
> +     .process = cli_ipv4_lookup,
> +     .usage = cli_ipv4_lookup_help,
> +};
> +
> +CLI_REGISTER(ipv4_lookup);
> diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
> new file mode 100644
> index 0000000000..2c5397f9d3
> --- /dev/null
> +++ b/app/graph/ip6_route.c
> @@ -0,0 +1,154 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_node_ip6_api.h>
> +
> +#include "module_api.h"
> +
> +static const char
> +cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via 
> <ip>";
> +
> +struct ipv6_route_config route6[MAX_ROUTE_ENTRIES];
> +
> +static uint8_t
> +convert_ip6_netmask_to_depth(uint8_t *netmask)
> +{
> +     uint8_t setbits = 0;
> +     uint8_t mask;
> +     int i;
> +
> +     for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
> +             mask = netmask[i];
> +             while (mask & 0x80) {
> +                     mask = mask << 1;
> +                     setbits++;
> +             }
> +     }
> +
> +     return setbits;
> +}
> +
> +static int
> +route_ip6_add(struct ipv6_route_config *route)
> +{
> +     int i, j;
> +
> +     for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
> +             if (!route6[i].is_used)
> +                     break;
> +     }
> +
> +     if (i == RTE_MAX_ETHPORTS)
> +             return -ENOMEM;
> +
> +     for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
> +             route6[i].ip[j] = route->ip[j];
> +             route6[i].mask[j] = route->mask[j];
> +             route6[i].gateway[j] = route->gateway[j];
> +     }
> +     route6[i].is_used = true;
> +
> +     return 0;
> +}
> +
> +int
> +route_ip6_add_to_lookup(void)
> +{
> +     struct ipv6_route_config *route = NULL;
> +     int rc = -EINVAL;
> +     uint8_t depth;
> +     int portid, i;
> +
> +     for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
> +             if (route6[i].is_used)
> +                     route = &route6[i];
> +
> +             portid = ethdev_portid_by_ip6(route->gateway);
> +             if (portid < 0) {
> +                     printf("Invalid portid found to install the route\n");
> +                     return rc;
> +             }
> +
> +             depth = convert_ip6_netmask_to_depth(route->mask);
> +
> +             rc = rte_node_ip6_route_add(route->ip, depth, portid,
> +                                          RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
> +             if (rc < 0)
> +                     return rc;
> +     }
> +
> +     return 0;
> +}
> +
> +static int
> +cli_ipv6_lookup_help(char **tokens __rte_unused, uint32_t n_tokens 
> __rte_unused,
> char *out,
> +                  size_t out_size, void *obj __rte_unused)
> +{
> +     size_t len;
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "\n%s\n",
> +              "--------------------------- ipv6_lookup command help 
> --------------------------
> ");
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_ipv6_lookup_help);
> +     return 0;
> +}
> +
> +static int
> +cli_ipv6_lookup(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> +             void *obj __rte_unused)
> +{
> +     struct ipv6_route_config config;
> +     int rc = -EINVAL;
> +
> +     if (n_tokens != 9) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             goto exit;
> +     }
> +
> +     if (parser_ip6_read(config.ip, tokens[4])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "ipv6");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[5], "netmask")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> +             goto exit;
> +     }
> +
> +     if (parser_ip6_read(config.mask, tokens[6])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[7], "via")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "via");
> +             goto exit;
> +     }
> +
> +     if (parser_ip6_read(config.gateway, tokens[8])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "gateway ip");
> +             goto exit;
> +     }
> +
> +     rc = route_ip6_add(&config);
> +     if (rc)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +     return rc;
> +}
> +
> +static struct cli_module ipv6_lookup = {
> +     .cmd = "ipv6_lookup",
> +     .process = cli_ipv6_lookup,
> +     .usage = cli_ipv6_lookup_help,
> +};
> +
> +CLI_REGISTER(ipv6_lookup);
> diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
> new file mode 100644
> index 0000000000..85b8b2618e
> --- /dev/null
> +++ b/app/graph/l3fwd.c
> @@ -0,0 +1,152 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_graph_worker.h>
> +#include <rte_lcore.h>
> +#include <rte_node_eth_api.h>
> +
> +#include "module_api.h"
> +
> +static char pcap_filename[RTE_GRAPH_PCAP_FILE_SZ];
> +static uint64_t packet_to_capture;
> +static int pcap_trace_enable;
> +
> +static int
> +l3fwd_pattern_configure(void)
> +{
> +     /* Graph initialization. 8< */
> +     static const char * const default_patterns[] = {
> +             "ip4*",
> +             "ethdev_tx-*",
> +             "pkt_drop",
> +     };
> +
> +     struct rte_graph_param graph_conf;
> +     const char **node_patterns;
> +     struct lcore_conf *qconf;
> +     uint16_t nb_patterns;
> +     uint8_t lcore_id;
> +     int rc;
> +
> +     nb_patterns = RTE_DIM(default_patterns);
> +     node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
> +                     sizeof(*node_patterns));
> +     if (!node_patterns)
> +             return -ENOMEM;
> +     memcpy(node_patterns, default_patterns,
> +                     nb_patterns * sizeof(*node_patterns));
> +
> +     memset(&graph_conf, 0, sizeof(graph_conf));
> +     graph_conf.node_patterns = node_patterns;
> +
> +     /* Pcap config */
> +     graph_conf.pcap_enable = pcap_trace_enable;
> +     graph_conf.num_pkt_to_capture = packet_to_capture;
> +     graph_conf.pcap_filename = pcap_filename;
> +
> +     for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +             rte_graph_t graph_id;
> +             rte_edge_t i;
> +
> +             if (rte_lcore_is_enabled(lcore_id) == 0)
> +                     continue;
> +
> +             qconf = &lcore_conf[lcore_id];
> +
> +             /* Skip graph creation if no source exists */
> +             if (!qconf->n_rx_queue)
> +                     continue;
> +
> +             /* Add rx node patterns of this lcore */
> +             for (i = 0; i < qconf->n_rx_queue; i++) {
> +                     graph_conf.node_patterns[nb_patterns + i] =
> +                             qconf->rx_queue_list[i].node_name;
> +             }
> +
> +             graph_conf.nb_node_patterns = nb_patterns + i;
> +             graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
> +
> +             snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
> +                             lcore_id);
> +
> +             graph_id = rte_graph_create(qconf->name, &graph_conf);
> +             if (graph_id == RTE_GRAPH_ID_INVALID)
> +                     rte_exit(EXIT_FAILURE,
> +                                     "rte_graph_create(): graph_id invalid"
> +                                     " for lcore %u\n", lcore_id);
> +
> +             qconf->graph_id = graph_id;
> +             qconf->graph = rte_graph_lookup(qconf->name);
> +             /* >8 End of graph initialization. */
> +             if (!qconf->graph)
> +                     rte_exit(EXIT_FAILURE,
> +                                     "rte_graph_lookup(): graph %s not 
> found\n",
> +                                     qconf->name);
> +     }
> +
> +     rc = route_ip4_add_to_lookup();
> +     if (rc < 0)
> +             rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup 
> table\n");
> +
> +     rc = route_ip6_add_to_lookup();
> +     if (rc < 0)
> +             rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup 
> table\n");
> +
> +     rc = neigh_ip4_add_to_rewrite();
> +     if (rc < 0)
> +             rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
> +
> +     rc = neigh_ip6_add_to_rewrite();
> +     if (rc < 0)
> +             rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
> +
> +     /* Launch per-lcore init on every worker lcore */
> +     rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
> +
> +     /* Accumulate and print stats on main until exit */
> +     if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
> +             graph_stats_print();
> +
> +     /* Wait for worker cores to exit */
> +     rc = 0;
> +     RTE_LCORE_FOREACH_WORKER(lcore_id) {
> +             rc = rte_eal_wait_lcore(lcore_id);
> +             /* Destroy graph */
> +             if (rc < 0 ||
> +                 
> rte_graph_destroy(rte_graph_from_name(lcore_conf[lcore_id].name)))
> {
> +                     rc = -1;
> +                     break;
> +             }
> +     }
> +     free(node_patterns);
> +
> +     ethdev_stop();
> +     return rc;
> +}
> +
> +int
> +usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t 
> nb_confs,
> uint16_t nb_graphs)
> +{
> +     int rc;
> +
> +     rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
> +     if (rc)
> +             rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
> +
> +     rc = l3fwd_pattern_configure();
> +     if (rc)
> +             rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
> +
> +     return rc;
> +}
> diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
> new file mode 100644
> index 0000000000..e1d23165e6
> --- /dev/null
> +++ b/app/graph/l3fwd.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_L3FWD_H
> +#define APP_GRAPH_L3FWD_H
> +
> +int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t 
> nb_conf,
> +                         uint16_t nb_graphs);
> +
> +#endif
> diff --git a/app/graph/main.c b/app/graph/main.c
> new file mode 100644
> index 0000000000..e9934025bf
> --- /dev/null
> +++ b/app/graph/main.c
> @@ -0,0 +1,201 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <fcntl.h>
> +#include <getopt.h>
> +#include <signal.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <rte_eal.h>
> +#include <rte_launch.h>
> +
> +#include "module_api.h"
> +
> +volatile bool force_quit;
> +
> +static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] 
> [--enable-graph-
> stats] "
> +                         "[--help]\n";
> +
> +static struct app_params {
> +     struct conn_params conn;
> +     char *script_name;
> +     bool enable_graph_stats;
> +} app = {
> +     .conn = {
> +             .welcome = "\nWelcome!\n\n",
> +             .prompt = "graph> ",
> +             .addr = "0.0.0.0",
> +             .port = 8086,
> +             .buf_size = 1024 * 1024,
> +             .msg_in_len_max = 1024,
> +             .msg_out_len_max = 1024 * 1024,
> +             .msg_handle = cli_process,
> +             .msg_handle_arg = NULL, /* set later. */
> +     },
> +     .script_name = NULL,
> +     .enable_graph_stats = false,
> +};
> +
> +static void
> +signal_handler(int signum)
> +{
> +     if (signum == SIGINT || signum == SIGTERM) {
> +             printf("\n\nSignal %d received, preparing to exit...\n", 
> signum);
> +             force_quit = true;
> +     }
> +}
> +
> +static int
> +app_args_parse(int argc, char **argv)
> +{
> +     struct option lgopts[] = {
> +             {"help", 0, 0, 'H'},
> +             {"enable-graph-stats", 0, 0, 'g'},
> +     };
> +     int h_present, p_present, s_present, n_args, i;
> +     char *app_name = argv[0];
> +     int opt, option_index;
> +
> +     /* Skip EAL input args */
> +     n_args = argc;
> +     for (i = 0; i < n_args; i++)
> +             if (strcmp(argv[i], "--") == 0) {
> +                     argc -= i;
> +                     argv += i;
> +                     break;
> +             }
> +
> +     if (i == n_args)
> +             return 0;
> +
> +     /* Parse args */
> +     h_present = 0;
> +     p_present = 0;
> +     s_present = 0;
> +
> +     while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) 
> != EOF) {
> +             switch (opt) {
> +             case 'h':
> +                     if (h_present) {
> +                             printf("Error: Multiple -h arguments\n");
> +                             return -1;
> +                     }
> +                     h_present = 1;
> +
> +                     if (!strlen(optarg)) {
> +                             printf("Error: Argument for -h not provided\n");
> +                             return -1;
> +                     }
> +
> +                     app.conn.addr = strdup(optarg);
> +                     if (app.conn.addr == NULL) {
> +                             printf("Error: Not enough memory\n");
> +                             return -1;
> +                     }
> +                     break;
> +
> +             case 'p':
> +                     if (p_present) {
> +                             printf("Error: Multiple -p arguments\n");
> +                             return -1;
> +                     }
> +                     p_present = 1;
> +
> +                     if (!strlen(optarg)) {
> +                             printf("Error: Argument for -p not provided\n");
> +                             return -1;
> +                     }
> +
> +                     app.conn.port = (uint16_t) atoi(optarg);
> +                     break;
> +
> +             case 's':
> +                     if (s_present) {
> +                             printf("Error: Multiple -s arguments\n");
> +                             return -1;
> +                     }
> +                     s_present = 1;
> +
> +                     if (!strlen(optarg)) {
> +                             printf("Error: Argument for -s not provided\n");
> +                             return -1;
> +                     }
> +
> +                     app.script_name = strdup(optarg);
> +                     if (app.script_name == NULL) {
> +                             printf("Error: Not enough memory\n");
> +                             return -1;
> +                     }
> +                     break;
> +
> +             case 'g':
> +                     app.enable_graph_stats = true;
> +                     break;
> +
> +             case 'H':
> +             default:
> +                     printf(usage, app_name);
> +                     return -1;
> +             }
> +     }
> +     optind = 1; /* reset getopt lib */
> +
> +     return 0;
> +}
> +
> +bool
> +app_graph_stats_enabled(void)
> +{
> +     return app.enable_graph_stats;
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> +     struct conn *conn;
> +     int rc;
> +
> +     /* Parse application arguments */
> +     rc = app_args_parse(argc, argv);
> +     if (rc < 0)
> +             return rc;
> +
> +     /* EAL */
> +     rc = rte_eal_init(argc, argv);
> +     if (rc < 0) {
> +             printf("Error: EAL initialization failed (%d)\n", rc);
> +             return rc;
> +     };
> +
> +     force_quit = false;
> +     signal(SIGINT, signal_handler);
> +     signal(SIGTERM, signal_handler);
> +
> +     /* Script */
> +     if (app.script_name) {
> +             cli_script_process(app.script_name, app.conn.msg_in_len_max,
> +                     app.conn.msg_out_len_max, NULL);
> +     }
> +
> +     /* Connectivity */
> +     app.conn.msg_handle_arg = NULL;
> +     conn = conn_init(&app.conn);
> +     if (!conn) {
> +             printf("Error: Connectivity initialization failed (%d)\n", rc);
> +             return rc;
> +     };
> +
> +     /* Dispatch loop */
> +     while (1) {
> +             conn_req_poll(conn);
> +
> +             conn_msg_poll(conn);
> +     }
> +
> +     /* clean up the EAL */
> +     rte_eal_cleanup();
> +}
> diff --git a/app/graph/mempool.c b/app/graph/mempool.c
> new file mode 100644
> index 0000000000..1cee66abed
> --- /dev/null
> +++ b/app/graph/mempool.c
> @@ -0,0 +1,134 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +#include <rte_mbuf.h>
> +
> +#include "mempool_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers
> <number_of_buffers> "
> +                  "cache <cache_size> numa <numa_id>";
> +
> +struct mempools mpconfig;
> +
> +int
> +mempool_process(struct mempool_config *config)
> +{
> +     struct rte_mempool *mp;
> +     uint8_t nb_pools;
> +
> +     nb_pools = mpconfig.nb_pools;
> +     strcpy(mpconfig.config[nb_pools].name, config->name);
> +     mpconfig.config[nb_pools].pool_size = config->pool_size;
> +     mpconfig.config[nb_pools].buffer_size = config->buffer_size;
> +     mpconfig.config[nb_pools].cache_size = config->cache_size;
> +     mpconfig.config[nb_pools].numa_node = config->numa_node;
> +
> +     mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config-
> >cache_size,
> +             64, config->buffer_size, config->numa_node);
> +     if (!mp)
> +             return -EINVAL;
> +
> +     mpconfig.mp[nb_pools] = mp;
> +     nb_pools++;
> +     mpconfig.nb_pools = nb_pools;
> +
> +     return 0;
> +}
> +
> +static int
> +cli_mempool_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, 
> char
> *out,
> +              size_t out_size, void *obj __rte_unused)
> +{
> +     size_t len;
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "\n%s\n",
> +              "---------------------------- mempool command help 
> ----------------------------");
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_mempool_help);
> +     return 0;
> +}
> +
> +static int
> +cli_mempool(char **tokens, uint32_t n_tokens, char *out, size_t out_size, 
> void *obj
> __rte_unused)
> +{
> +     uint32_t pkt_buffer_size, pool_size, cache_size, numa_node;
> +     struct mempool_config config;
> +     int rc = -EINVAL;
> +
> +     if (n_tokens != 10) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[2], "size")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "size");
> +             goto exit;
> +     }
> +
> +     if (parser_uint32_read(&pkt_buffer_size, tokens[3])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "mbuf_size");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[4], "buffers")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "buffers");
> +             goto exit;
> +     }
> +
> +     if (parser_uint32_read(&pool_size, tokens[5])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "number_of_buffers");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[6], "cache")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "cache");
> +             goto exit;
> +     }
> +
> +     if (parser_uint32_read(&cache_size, tokens[7])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "cache_size");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[8], "numa")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "numa");
> +             goto exit;
> +     }
> +
> +     if (parser_uint32_read(&numa_node, tokens[9])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "numa_id");
> +             goto exit;
> +     }
> +
> +     strcpy(config.name, tokens[1]);
> +     config.name[strlen(tokens[1])] = '\0';
> +     config.pool_size = pool_size;
> +     config.buffer_size = pkt_buffer_size;
> +     config.cache_size = cache_size;
> +     config.numa_node = numa_node;
> +
> +     rc = mempool_process(&config);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +     return rc;
> +}
> +
> +static struct cli_module mempool = {
> +     .cmd = "mempool",
> +     .process = cli_mempool,
> +     .usage = cli_mempool_help,
> +};
> +
> +CLI_REGISTER(mempool);
> diff --git a/app/graph/mempool.h b/app/graph/mempool.h
> new file mode 100644
> index 0000000000..5fc788199d
> --- /dev/null
> +++ b/app/graph/mempool.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MEMPOOL_H
> +#define APP_GRAPH_MEMPOOL_H
> +
> +struct mempool_config {
> +     char name[RTE_MEMPOOL_NAMESIZE];
> +     int pool_size;
> +     int cache_size;
> +     int buffer_size;
> +     int numa_node;
> +};
> +
> +int mempool_process(struct mempool_config *config);
> +
> +#endif
> diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
> new file mode 100644
> index 0000000000..5a55722b32
> --- /dev/null
> +++ b/app/graph/mempool_priv.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MEMPOOL_PRIV_H
> +#define APP_GRAPH_MEMPOOL_PRIV_H
> +
> +#include "mempool.h"
> +
> +struct mempools {
> +     struct mempool_config config[RTE_MAX_ETHPORTS];
> +     struct rte_mempool *mp[RTE_MAX_ETHPORTS];
> +     uint8_t nb_pools;
> +};
> +
> +#endif
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> new file mode 100644
> index 0000000000..a3011e504b
> --- /dev/null
> +++ b/app/graph/meson.build
> @@ -0,0 +1,25 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2023 Marvell.
> +
> +# override default name to drop the hyphen
> +name = 'graph'
> +build = cc.has_header('sys/epoll.h')
> +if not build
> +    subdir_done()
> +endif
> +
> +deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node']
> +sources = files(
> +        'cli.c',
> +        'conn.c',
> +        'ethdev_rx.c',
> +        'ethdev.c',
> +        'graph.c',
> +        'ip4_route.c',
> +        'ip6_route.c',
> +        'main.c',
> +        'mempool.c',
> +        'neigh.c',
> +        'l3fwd.c',
> +        'utils.c',
> +)
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> new file mode 100644
> index 0000000000..09b10bc672
> --- /dev/null
> +++ b/app/graph/module_api.h
> @@ -0,0 +1,33 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MODULE_API_H
> +#define APP_GRAPH_MODULE_API_H
> +
> +#include <stdint.h>
> +
> +#include <rte_common.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#include "conn.h"
> +#include "cli.h"
> +#include "ethdev.h"
> +#include "ethdev_rx.h"
> +#include "graph.h"
> +#include "l3fwd.h"
> +#include "mempool.h"
> +#include "neigh.h"
> +#include "route.h"
> +#include "utils.h"
> +
> +/*
> + * Externs
> + */
> +extern volatile bool force_quit;
> +
> +bool app_graph_stats_enabled(void);
> +
> +#endif
> diff --git a/app/graph/neigh.c b/app/graph/neigh.c
> new file mode 100644
> index 0000000000..07766758c9
> --- /dev/null
> +++ b/app/graph/neigh.c
> @@ -0,0 +1,269 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_node_ip4_api.h>
> +#include <rte_node_ip6_api.h>
> +
> +#include "neigh_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
> +
> +static const char
> +cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
> +
> +struct ipv4_neigh_config neigh4[MAX_NEIGH_ENTRIES];
> +struct ipv6_neigh_config neigh6[MAX_NEIGH_ENTRIES];
> +
> +static int
> +neigh_ip4_add(uint32_t ip, uint64_t mac)
> +{
> +     int i;
> +
> +     for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> +             if (!neigh4[i].is_used)
> +                     break;
> +     }
> +
> +     if (i == MAX_NEIGH_ENTRIES)
> +             return -ENOMEM;
> +
> +     neigh4[i].ip = ip;
> +     neigh4[i].mac = mac;
> +     neigh4[i].is_used = true;
> +     return 0;
> +}
> +
> +static int
> +neigh_ip6_add(uint8_t *ip, uint64_t mac)
> +{
> +     int i, j;
> +
> +     for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> +             if (!neigh6[i].is_used)
> +                     break;
> +     }
> +
> +     if (i == MAX_NEIGH_ENTRIES)
> +             return -ENOMEM;
> +
> +     for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
> +             neigh6[i].ip[j] = ip[j];
> +
> +     neigh6[i].mac = mac;
> +     neigh6[i].is_used = true;
> +     return 0;
> +}
> +
> +int
> +neigh_ip4_add_to_rewrite(void)
> +{
> +     uint8_t data[2 * RTE_ETHER_ADDR_LEN];
> +     uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
> +     struct rte_ether_addr smac = {0};
> +     struct ipv4_neigh_config *neigh;
> +     int16_t portid = 0;
> +     int rc, i;
> +
> +     for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> +             if (!neigh4[i].is_used)
> +                     continue;
> +
> +             neigh = &neigh4[i];
> +             portid = ethdev_portid_by_ip4(neigh->ip);
> +             if (portid < 0) {
> +                     printf("Invalid portid found to add  neigh\n");
> +                     return -EINVAL;
> +             }
> +
> +             memset(data, 0, len);
> +
> +             /* Copy dst mac */
> +             rte_memcpy((void *)&data[0], (void *)&neigh->mac,
> RTE_ETHER_ADDR_LEN);
> +
> +             /* Copy src mac */
> +             rc = rte_eth_macaddr_get(portid, &smac);
> +             if (rc < 0) {
> +                     printf("Cannot get MAC address: err=%d, port=%d\n", rc, 
> portid);
> +                     return rc;
> +             }
> +
> +             rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes,
> RTE_ETHER_ADDR_LEN);
> +
> +             rc = rte_node_ip4_rewrite_add(portid, data, len, portid);
> +             if (rc < 0) {
> +                     printf("Error in writing rewrite data: err=%d, 
> port=%d\n", rc, portid);
> +                     return rc;
> +             }
> +     }
> +
> +     return 0;
> +}
> +
> +int
> +neigh_ip6_add_to_rewrite(void)
> +{
> +     uint8_t data[2 * RTE_ETHER_ADDR_LEN];
> +     uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
> +     struct rte_ether_addr smac = {0};
> +     struct ipv6_neigh_config *neigh;
> +     int16_t portid = 0;
> +     int rc, i;
> +
> +     for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> +             if (!neigh6[i].is_used)
> +                     continue;
> +
> +             neigh = &neigh6[i];
> +             portid = ethdev_portid_by_ip6(neigh->ip);
> +             if (portid < 0) {
> +                     printf("Invalid portid found to add neigh\n");
> +                     return -EINVAL;
> +             }
> +
> +             memset(data, 0, len);
> +
> +             /* Copy dst mac */
> +             rte_memcpy((void *)&data[0], (void *)&neigh->mac,
> RTE_ETHER_ADDR_LEN);
> +
> +             /* Copy src mac */
> +             rc = rte_eth_macaddr_get(portid, &smac);
> +             if (rc < 0) {
> +                     printf("Cannot get MAC address: err=%d, port=%d\n",
> +                             rc, portid);
> +                     return rc;
> +             }
> +
> +             rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes,
> RTE_ETHER_ADDR_LEN);
> +
> +             rc = rte_node_ip6_rewrite_add(portid, data, len, portid);
> +             if (rc < 0) {
> +                     printf("Error in writing rewrite data: err=%d, 
> port=%d\n", rc, portid);
> +                     return rc;
> +             }
> +     }
> +
> +     return 0;
> +}
> +
> +static int
> +cmd_neigh_v4(char **tokens, uint32_t n_tokens, char *out, size_t out_size, 
> void *obj
> __rte_unused)
> +{
> +     int rc = -EINVAL;
> +     uint64_t mac;
> +     uint32_t ip;
> +
> +     if (n_tokens != 5) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[1], "add")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[2], "ipv4")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "ipv4");
> +             goto exit;
> +     }
> +
> +     if (parser_ip4_read(&ip, tokens[3])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> +             goto exit;
> +     }
> +
> +     if (parser_mac_read(&mac, tokens[4])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "mac");
> +             goto exit;
> +     }
> +
> +     rc = neigh_ip4_add(ip, mac);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +     return rc;
> +}
> +
> +static int
> +cmd_neigh_v6(char **tokens, uint32_t n_tokens, char *out, size_t out_size, 
> void *obj
> __rte_unused)
> +{
> +     uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
> +     int rc = -EINVAL;
> +     uint64_t mac;
> +
> +     if (n_tokens != 5) {
> +             snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[1], "add")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> +             goto exit;
> +     }
> +
> +     if (strcmp(tokens[2], "ipv6")) {
> +             snprintf(out, out_size, MSG_ARG_NOT_FOUND, "ipv6");
> +             goto exit;
> +     }
> +
> +     if (parser_ip6_read(ip, tokens[3])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> +             goto exit;
> +     }
> +
> +     if (parser_mac_read(&mac, tokens[4])) {
> +             snprintf(out, out_size, MSG_ARG_INVALID, "mac");
> +             goto exit;
> +     }
> +
> +     rc = neigh_ip6_add(ip, mac);
> +     if (rc < 0)
> +             snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +     return rc;
> +}
> +
> +static int
> +cli_neigh_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, 
> char *out,
> +                  size_t out_size, void *obj __rte_unused)
> +{
> +     size_t len;
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "\n%s\n",
> +              "----------------------------- neigh command help 
> -----------------------------");
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_neigh_v4_help);
> +
> +     len = strlen(out);
> +     snprintf(out + len, out_size, "%s\n", cmd_neigh_v6_help);
> +     return 0;
> +}
> +
> +static int
> +cli_neigh(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void 
> *obj)
> +{
> +     if (strcmp(tokens[2], "ipv4") == 0)
> +             return cmd_neigh_v4(tokens, n_tokens, out, out_size, obj);
> +     else
> +             return cmd_neigh_v6(tokens, n_tokens, out, out_size, obj);
> +}
> +
> +static struct cli_module neigh = {
> +     .cmd = "neigh",
> +     .process = cli_neigh,
> +     .usage = cli_neigh_help,
> +};
> +
> +CLI_REGISTER(neigh);

[Nithin] Move logic for tokenizing into cmdline library itself. Module should 
only register the commands to libcmdline.

> diff --git a/app/graph/neigh.h b/app/graph/neigh.h
> new file mode 100644
> index 0000000000..3964c37bb0
> --- /dev/null
> +++ b/app/graph/neigh.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_NEIGH_H
> +#define APP_GRAPH_NEIGH_H
> +
> +int neigh_ip4_add_to_rewrite(void);
> +int neigh_ip6_add_to_rewrite(void);
> +
> +#endif
> diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
> new file mode 100644
> index 0000000000..745dc7d671
> --- /dev/null
> +++ b/app/graph/neigh_priv.h
> @@ -0,0 +1,22 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_NEIGH_PRIV_H
> +#define APP_GRAPH_NEIGH_PRIV_H
> +
> +#define MAX_NEIGH_ENTRIES 32
> +
> +struct ipv4_neigh_config {
> +     uint32_t ip;
> +     uint64_t mac;
> +     bool is_used;
> +};
> +
> +struct ipv6_neigh_config {
> +     uint8_t ip[16];
> +     uint64_t mac;
> +     bool is_used;
> +};
> +
> +#endif
> diff --git a/app/graph/route.h b/app/graph/route.h
> new file mode 100644
> index 0000000000..6b4acf3344
> --- /dev/null
> +++ b/app/graph/route.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ROUTE_H
> +#define APP_GRAPH_ROUTE_H
> +
> +#define MAX_ROUTE_ENTRIES 32
> +
> +struct ipv4_route_config {
> +     uint32_t ip;
> +     uint32_t netmask;
> +     uint32_t via;
> +     bool is_used;
> +};
> +
> +struct ipv6_route_config {
> +     uint8_t ip[16];
> +     uint8_t mask[16];
> +     uint8_t gateway[16];
> +     bool is_used;
> +};
> +
> +extern struct ipv4_route_config route4[MAX_ROUTE_ENTRIES];
> +extern struct ipv6_route_config route6[MAX_ROUTE_ENTRIES];
> +
> +int route_ip4_add_to_lookup(void);
> +int route_ip6_add_to_lookup(void);
> +
> +#endif
> diff --git a/app/graph/utils.c b/app/graph/utils.c
> new file mode 100644
> index 0000000000..48a83e738c
> --- /dev/null
> +++ b/app/graph/utils.c
> @@ -0,0 +1,155 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <ctype.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +
> +#include "module_api.h"
> +
> +#define white_spaces_skip(pos)                       \
> +({                                           \
> +     __typeof__(pos) _p = (pos);             \
> +     for ( ; isspace(*_p); _p++)             \
> +             ;                               \
> +     _p;                                     \
> +})
> +
> +static void
> +hex_string_to_uint64(uint64_t *dst, const char *hexs)
> +{
> +     char buf[2] = {0};
> +     uint8_t shift = 4;
> +     int iter = 0;
> +     char c;
> +
> +     while ((c = *hexs++)) {
> +             buf[0] = c;
> +             *dst |= (strtol(buf, NULL, 16) << shift);
> +             shift -= 4;
> +             iter++;
> +             if (iter == 2) {
> +                     iter = 0;
> +                     shift = 4;
> +                     dst++;
> +             }
> +     }
> +}
> +
> +int
> +parser_uint64_read(uint64_t *value, const char *p)
> +{
> +     char *next;
> +     uint64_t val;
> +
> +     p = white_spaces_skip(p);
> +     if (!isdigit(*p))
> +             return -EINVAL;
> +
> +     val = strtoul(p, &next, 0);
> +     if (p == next)
> +             return -EINVAL;
> +
> +     p = next;
> +     switch (*p) {
> +     case 'T':
> +             val *= 1024ULL;
> +             /* fall through */
> +     case 'G':
> +             val *= 1024ULL;
> +             /* fall through */
> +     case 'M':
> +             val *= 1024ULL;
> +             /* fall through */
> +     case 'k':
> +     case 'K':
> +             val *= 1024ULL;
> +             p++;
> +             break;
> +     }
> +
> +     p = white_spaces_skip(p);
> +     if (*p != '\0')
> +             return -EINVAL;
> +
> +     *value = val;
> +     return 0;
> +}
> +
> +int
> +parser_uint32_read(uint32_t *value, const char *p)
> +{
> +     uint64_t val = 0;
> +     int rc = parser_uint64_read(&val, p);
> +
> +     if (rc < 0)
> +             return rc;
> +
> +     if (val > UINT32_MAX)
> +             return -ERANGE;
> +
> +     *value = val;
> +     return 0;
> +}
> +
> +int
> +parser_ip4_read(uint32_t *value, char *p)
> +{
> +     uint8_t shift = 24;
> +     uint32_t ip = 0;
> +     char *token;
> +
> +     token = strtok(p, ".");
> +     while (token != NULL) {
> +             ip |= (atoi(token) << shift);
> +             token = strtok(NULL, ".");
> +             shift -= 8;
> +     }
> +
> +     *value = ip;
> +
> +     return 0;
> +}
> +
> +int
> +parser_ip6_read(uint8_t *value, char *p)
> +{
> +     uint64_t val = 0;
> +     char *token;
> +
> +     token = strtok(p, ":");
> +     while (token != NULL) {
> +             hex_string_to_uint64(&val, token);
> +             *value = val;
> +             token = strtok(NULL, ":");
> +             value++;
> +             val = 0;
> +     }
> +
> +     return 0;
> +}
> +
> +int
> +parser_mac_read(uint64_t *value, char *p)
> +{
> +     uint64_t mac = 0, val = 0;
> +     uint8_t shift = 40;
> +     char *token;
> +
> +     token = strtok(p, ":");
> +     while (token != NULL) {
> +             hex_string_to_uint64(&val, token);
> +             mac |= val << shift;
> +             token = strtok(NULL, ":");
> +             shift -= 8;
> +             val = 0;
> +     }
> +
> +     *value = mac;
> +
> +     return 0;
> +}
> diff --git a/app/graph/utils.h b/app/graph/utils.h
> new file mode 100644
> index 0000000000..0ebb5de55a
> --- /dev/null
> +++ b/app/graph/utils.h
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_UTILS_H
> +#define APP_GRAPH_UTILS_H
> +
> +int parser_uint64_read(uint64_t *value, const char *p);
> +int parser_uint32_read(uint32_t *value, const char *p);
> +int parser_ip4_read(uint32_t *value, char *p);
> +int parser_ip6_read(uint8_t *value, char *p);
> +int parser_mac_read(uint64_t *value, char *p);
> +
> +#endif
> diff --git a/app/meson.build b/app/meson.build
> index e4bf5c531c..728c936383 100644
> --- a/app/meson.build
> +++ b/app/meson.build
> @@ -17,6 +17,7 @@ endif
>  apps = [
>          'dumpcap',
>          'pdump',
> +        'graph',
>          'proc-info',
>          'test-acl',
>          'test-bbdev',
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> new file mode 100644
> index 0000000000..c90dc9ad7f
> --- /dev/null
> +++ b/doc/guides/tools/graph.rst
> @@ -0,0 +1,171 @@
> +..  SPDX-License-Identifier: BSD-3-Clause
> +    Copyright(c) 2023 Marvell.
> +
> +dpdk-graph Application
> +======================
> +
> +The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
> +application that allows exercising various graph use cases.
> +This application has a generic framework to add new graph based use cases to
> +verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
> +Based on the input file, application creates a graph to cater the use case.
> +
> +Supported Use cases
> +-------------------
> + * l3fwd
> +
> +Running the Application
> +-----------------------
> +
> +The application has a number of command line options which can be provided in
> +following syntax
> +
> +.. code-block:: console
> +
> +   dpdk-graph [EAL Options] -- [application options]
> +
> +EAL Options
> +~~~~~~~~~~~
> +
> +Following are the EAL command-line options that can be used in conjunction
> +with the ``dpdk-graph`` application.
> +See the DPDK Getting Started Guides for more information on these options.
> +
> +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> +
> +        Set the hexadecimal bit mask of the cores to run on. The CORELIST is 
> a
> +        list of cores to be used.
> +
> +Application Options
> +~~~~~~~~~~~~~~~~~~~
> +
> +Following are the application command-line options:
> +
> +* ``-h``
> +
> +        Set the host IPv4 address over which telnet session can be opened.
> +        It is an optional parameter. Default host address is 0.0.0.0.
> +
> +* ``-p``
> +
> +        Set the L4 port number over which telnet session can be opened.
> +     It is an optional parameter. Default port is 8086.
> +
> +* ``-s``
> +
> +        Script name with absolute path which specifies the use case. It is
> +        a mandatory parameter which will be used to create desired graph
> +        for a given use case.
> +
> +* ``--enable-graph-stats``
> +
> +       Enable graph statistics printing on console. By default graph 
> statistics are disabled.
> +
> +* ``--help``
> +
> +       Dumps application usage
> +
> +Supported CLI commands
> +----------------------
> +
> +This section provides details on commands which can be used in 
> ``<usecase>.cli``
> +file to express the requested use case configuration.
> +
> +.. list-table:: Exposed CLIs
> +   :header-rows: 1
> +   :widths: auto
> +
> +   * - Command
> +     - Description
> +     - Dynamic
> +     - Optional
> +   * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model 
> <rtc | mcd |
> default>
> +     - Command to express the desired use case
> +     - No
> +     - No
> +   * - graph start
> +     - Command to start the graph.
> +       This command triggers that no more commands are left to be parsed and 
> graph
> +       initialization can be started now. It must be the last command in 
> ``<usecase>.cli``
> +     - No
> +     - No
> +   * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> 
> cache
> <cache_size> numa <numa_id>
> +     - Command to create mempool which will be further associated to RxQ to 
> dequeue the
> packets
> +     - No
> +     - No
> +   * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name> [mtu
> <mtu_sz>]
> +     - Command to create DPDK port with given number of Rx and Tx queues. 
> Also attached
> +       RxQ with given mempool. Each port can have single mempool only i.e. 
> all RxQs will
> +       share the same mempool.
> +     - No
> +     - No
> +   * - ethdev <ethdev_name> mtu <mtu_sz>
> +     - Command to configure MTU of DPDK port
> +     - Yes
> +     - Yes
> +   * - ethdev <ethdev_name> promiscuous <on/off>
> +     - Command to enable/disable promiscuous mode on DPDK port
> +     - Yes
> +     - Yes
> +   * - ethdev <ethdev_name> show
> +     - Command to dump current ethdev configuration
> +     - Yes
> +     - Yes
> +   * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
> +     - Command to configure IPv4 address on given PCI device. It is needed 
> if user
> +       wishes to use ``ipv4_lookup`` node
> +     - No
> +     - Yes
> +   * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
> +     - Command to configure IPv6 address on given PCI device. It is needed 
> if user
> +       wishes to use ``ipv6_lookup`` node
> +     - No
> +     - Yes
> +   * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
> +     - Command to add a route into ``ipv4_lookup`` LPM table. It is needed 
> if user
> +       wishes to route the packets based on LPM lookup table.
> +     - No
> +     - Yes
> +   * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
> +     - Command to add a route into ``ipv6_lookup`` LPM table. It is needed 
> if user
> +       wishes to route the packets based on LPM6 lookup table.
> +     - No
> +     - Yes
> +   * - neigh add ipv4 <ip> <mac>
> +     - Command to add a neighbour information into ``ipv4_rewrite`` node.
> +     - No
> +     - Yes
> +   * - neigh add ipv6 <ip> <mac>
> +     - Command to add a neighbour information into ``ipv6_rewrite`` node.
> +     - No
> +     - Yes
> +   * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
> +     - Command to add port-queue-core mapping to ``ethdev_rx`` node. 
> ``ethdev_rx``
> +       node instance will be pinned on given core and will poll on requested
> +       port/queue pair.
> +     - No
> +     - No
> +

[Nithin] Add CLI to get ethdev stats, graph stats via telnet

> +Runtime configuration
> +---------------------
> +
> +Application allows some configuration to be modified at runtime using a 
> telnet session.
> +Application initiates a telnet server with host address 0.0.0.0 and port 
> number 8086 if
> +``-h`` and ``-p`` is not given otherwise user provided IPv4 address and port 
> number will
> +be used.
> +
> +After successful launch of application, client can connect to using 
> host/port address and
> +console will be accessed with prompt ``graph>``.

[Nithin] Update Telnet session connection example with log

> +
> +Created graph for use case
> +--------------------------
> +
> +On the successful execution of ``<usecase>.cli`` file, corresponding graph 
> will be created.
> +This section mentions the created graph for each use case.
> +
> +l3fwd
> +~~~~~
> +
> +.. _figure_l3fwd_graph:
> +
> +.. figure:: img/graph-usecase-l3fwd.*
> diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg 
> b/doc/guides/tools/img/graph-
> usecase-l3fwd.svg
> new file mode 100644
> index 0000000000..3b991c4cf0
> --- /dev/null
> +++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
> @@ -0,0 +1,210 @@
> +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
> + "https://urldefense.proofpoint.com/v2/url?u=http-
> 3A__www.w3.org_Graphics_SVG_1.1_DTD_svg11.dtd&d=DwIDAg&c=nKjWec2b6R0mOyPa
> z7xtfQ&r=FZ_tPCbgFOh18zwRPO9H0yDx8VW38vuapifdDfc8SFQ&m=Ee0JsG0yj736hYMlfAp
> ezlB2TtUrWk19QRB6M5nl_A9wsLObEPqlXYSqUau7ZfBV&s=QNoHRxZ4WaV84k-
> 6IlzN8d9bMQsg8Go9iFXzE0lgT-o&e= ">
> +<!-- Generated by graphviz version 2.43.0 (0)
> + -->
> +<!-- SPDX-License-Identifier: BSD-3-Clause -->
> +<!-- Copyright(C) 2023 Marvell. -->
> +<!--
> +
> +Generated with following command
> +dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
> +
> +cat dot.dot
> +digraph dpdk_app_graph_l3fwd_nodes_flow {
> +    ingress_port [shape=rect]
> +    ethdev_rx
> +    pkt_cls
> +    ip4_lookup
> +    ip6_lookup
> +    ip4_rewrite
> +    ip6_rewrite
> +    ethdev_tx
> +    pkt_drop
> +    egress_port  [shape=rect]
> +
> +    ingress_port -> ethdev_rx [label="ingress packet"]
> +
> +    ethdev_rx -> pkt_cls
> +
> +    pkt_cls -> ip4_lookup [color="green"]
> +    pkt_cls -> ip6_lookup [color="blue"]
> +    pkt_cls -> pkt_drop   [color="red" style="dashed"]
> +
> +    ip4_lookup -> ip4_rewrite [color="green"]
> +    ip4_lookup -> pkt_drop [color="red" style="dashed"]
> +
> +    ip6_lookup -> ip6_rewrite [color="blue"]
> +    ip6_lookup -> pkt_drop [color="red" style="dashed"]
> +
> +    ip4_rewrite -> ethdev_tx [color="green"]
> +    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
> +
> +    ip6_rewrite -> ethdev_tx [color="blue"]
> +    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
> +
> +    ethdev_tx -> egress_port [label="egress packet"]
> +    ethdev_tx -> pkt_drop [color="red" style="dashed"]
> +}
> +
> + -->
> +<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
> +<svg width="550pt" height="510pt"
> + viewBox="0.00 0.00 549.50 510.00"
> xmlns="https://urldefense.proofpoint.com/v2/url?u=http-
> 3A__www.w3.org_2000_svg&d=DwIDAg&c=nKjWec2b6R0mOyPaz7xtfQ&r=FZ_tPCbgFOh1
> 8zwRPO9H0yDx8VW38vuapifdDfc8SFQ&m=Ee0JsG0yj736hYMlfApezlB2TtUrWk19QRB6M5nl
> _A9wsLObEPqlXYSqUau7ZfBV&s=KLiZ62_Z8HSL_a9Mq0OR-PCG_h1JavRUfbKsPOc4IAo&e=
> " xmlns:xlink="http://www.w3.org/1999/xlink";>
> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 
> 506)">
> +<title>dpdk_app_graph_l3fwd_nodes_flow</title>
> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 
> 545.5,4 -4,4"/>
> +<!-- ingress_port -->
> +<g id="node1" class="node">
> +<title>ingress_port</title>
> +<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 
> 489.5,-466
> 489.5,-502"/>
> +<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" 
> font-
> size="14.00">ingress_port</text>
> +</g>
> +<!-- ethdev_rx -->
> +<g id="node2" class="node">
> +<title>ethdev_rx</title>
> +<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
> +<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" 
> font-
> size="14.00">ethdev_rx</text>
> +</g>
> +<!-- ingress_port&#45;&gt;ethdev_rx -->
> +<g id="edge1" class="edge">
> +<title>ingress_port&#45;&gt;ethdev_rx</title>
> +<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 
> 436.5,-438.55 436.5,-
> 425.24"/>
> +<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 
> 433,-425.18 440,-
> 425.18"/>
> +<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-
> size="14.00">ingress packet</text>
> +</g>
> +<!-- pkt_cls -->
> +<g id="node3" class="node">
> +<title>pkt_cls</title>
> +<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
> +<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" 
> font-
> size="14.00">pkt_cls</text>
> +</g>
> +<!-- ethdev_rx&#45;&gt;pkt_cls -->
> +<g id="edge2" class="edge">
> +<title>ethdev_rx&#45;&gt;pkt_cls</title>
> +<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 
> 436.5,-361.05 436.5,-
> 352.07"/>
> +<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 
> 433,-352.03 440,-
> 352.03"/>
> +</g>
> +<!-- ip4_lookup -->
> +<g id="node4" class="node">
> +<title>ip4_lookup</title>
> +<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
> +<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" 
> font-
> size="14.00">ip4_lookup</text>
> +</g>
> +<!-- pkt_cls&#45;&gt;ip4_lookup -->
> +<g id="edge3" class="edge">
> +<title>pkt_cls&#45;&gt;ip4_lookup</title>
> +<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 
> 436.5,-288.05 436.5,-
> 279.07"/>
> +<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 
> 433,-279.03 440,-
> 279.03"/>
> +</g>
> +<!-- ip6_lookup -->
> +<g id="node5" class="node">
> +<title>ip6_lookup</title>
> +<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
> +<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" 
> font-
> size="14.00">ip6_lookup</text>
> +</g>
> +<!-- pkt_cls&#45;&gt;ip6_lookup -->
> +<g id="edge4" class="edge">
> +<title>pkt_cls&#45;&gt;ip6_lookup</title>
> +<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 
> 359.66,-283.75
> 335.97,-271.65"/>
> +<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 
> 334.21,-274.68
> 337.39,-268.45"/>
> +</g>
> +<!-- pkt_drop -->
> +<g id="node9" class="node">
> +<title>pkt_drop</title>
> +<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
> +<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" 
> font-
> size="14.00">pkt_drop</text>
> +</g>
> +<!-- pkt_cls&#45;&gt;pkt_drop -->
> +<g id="edge5" class="edge">
> +<title>pkt_cls&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" 
> d="M468.77,-311.93C493.88,-
> 301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 
> 467.5,-34.79
> 420.91,-25.78"/>
> +<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 
> 420.08,-29.18 421.31,-
> 22.29"/>
> +</g>
> +<!-- ip4_rewrite -->
> +<g id="node6" class="node">
> +<title>ip4_rewrite</title>
> +<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
> +<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" 
> font-
> size="14.00">ip4_rewrite</text>
> +</g>
> +<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
> +<g id="edge6" class="edge">
> +<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
> +<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 
> 415.38,-214.29
> 409.79,-204.85"/>
> +<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 
> 406.75,-206.59
> 412.78,-203.02"/>
> +</g>
> +<!-- ip4_lookup&#45;&gt;pkt_drop -->
> +<g id="edge7" class="edge">
> +<title>ip4_lookup&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" 
> d="M449.33,-233.03C456.19,-
> 222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 
> 451.61,-112.41
> 412.64,-67.99 386.65,-42.17"/>
> +<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 
> 384.09,-44.56 388.97,-
> 39.54"/>
> +</g>
> +<!-- ip6_rewrite -->
> +<g id="node7" class="node">
> +<title>ip6_rewrite</title>
> +<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
> +<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" 
> font-
> size="14.00">ip6_rewrite</text>
> +</g>
> +<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
> +<g id="edge8" class="edge">
> +<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
> +<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 
> 251.31,-212.31
> 238.52,-201.87"/>
> +<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 
> 236,-204.33
> 240.43,-198.9"/>
> +</g>
> +<!-- ip6_lookup&#45;&gt;pkt_drop -->
> +<g id="edge9" class="edge">
> +<title>ip6_lookup&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" 
> d="M302.02,-232.72C306.79,-
> 214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
> +<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 
> 350.35,-44.98 357.08,-
> 46.92"/>
> +</g>
> +<!-- ethdev_tx -->
> +<g id="node8" class="node">
> +<title>ethdev_tx</title>
> +<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
> +<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" 
> font-
> size="14.00">ethdev_tx</text>
> +</g>
> +<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
> +<g id="edge10" class="edge">
> +<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
> +<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 
> 311.81,-136.51
> 287.98,-124.84"/>
> +<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 
> 286.31,-127.92
> 289.39,-121.63"/>
> +</g>
> +<!-- ip4_rewrite&#45;&gt;pkt_drop -->
> +<g id="edge11" class="edge">
> +<title>ip4_rewrite&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" 
> d="M390.91,-159.79C385.2,-132.48
> 374.03,-78.99 367.22,-46.38"/>
> +<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 
> 363.71,-46.69 370.56,-
> 45.26"/>
> +</g>
> +<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
> +<g id="edge12" class="edge">
> +<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
> +<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 
> 230,-141.51 235.14,-
> 132.14"/>
> +<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 
> 232.17,-130.28
> 238.31,-133.65"/>
> +</g>
> +<!-- ip6_rewrite&#45;&gt;pkt_drop -->
> +<g id="edge13" class="edge">
> +<title>ip6_rewrite&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" 
> d="M197.68,-160.05C184.87,-
> 140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
> +<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 
> 301.19,-21.33 302.35,-
> 28.24"/>
> +</g>
> +<!-- ethdev_tx&#45;&gt;pkt_drop -->
> +<g id="edge15" class="edge">
> +<title>ethdev_tx&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" 
> d="M270.3,-88.21C287.91,-74.85
> 313.31,-55.57 332.84,-40.75"/>
> +<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 
> 330.73,-37.96 334.96,-
> 43.54"/>
> +</g>
> +<!-- egress_port -->
> +<g id="node10" class="node">
> +<title>egress_port</title>
> +<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 
> 101,-36"/>
> +<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-
> size="14.00">egress_port</text>
> +</g>
> +<!-- ethdev_tx&#45;&gt;egress_port -->
> +<g id="edge14" class="edge">
> +<title>ethdev_tx&#45;&gt;egress_port</title>
> +<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 
> 136.54,-55.75 99.95,-
> 40.12"/>
> +<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 
> 98.28,-43.21
> 101.03,-36.78"/>
> +<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" 
> font-
> size="14.00">egress packet</text>
> +</g>
> +</g>
> +</svg>
> diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
> index f2afb1fcc5..4f4dc8b518 100644
> --- a/doc/guides/tools/index.rst
> +++ b/doc/guides/tools/index.rst
> @@ -23,4 +23,5 @@ DPDK Tools User Guides
>      testeventdev
>      testregex
>      testmldev
> +    graph
>      dts
> --
> 2.25.1

Reply via email to