[v11,02/12] app/graph: support telnet connectivity framework

Message ID 20231019173011.1186656-3-skori@marvell.com (mailing list archive)
State Accepted, archived
Delegated to: Thomas Monjalon
Headers
Series add CLI based graph application |

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

Sunil Kumar Kori Oct. 19, 2023, 5:30 p.m. UTC
  From: Sunil Kumar Kori <skori@marvell.com>

Adds framework to initiate a telnet session with application.

Some configurations and debug commands are exposed as runtime APIs.
Those commands can be invoked using telnet session.

Application initiates a telnet server with host address 0.0.0.0
and port number 8086 by default.

To make it configurable, "-h" and "-p" options are provided.
Using them user can pass host address and port number on which
application will start telnet server.

Using same host address and port number, telnet client can connect
to application.

Syntax to connect with application:
	# telnet <host> <port>

Once session is connected, "graph> " prompt will be available.
Example:
	# telnet 10.28.35.207 50000
	  Trying 10.28.35.207...
	  Connected to 10.28.35.207.
	  Escape character is '^]'.

	  Welcome!

	  graph>

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/conn.c           | 284 +++++++++++++++++++++++++++++++++++++
 app/graph/conn.h           |  46 ++++++
 app/graph/main.c           | 103 +++++++++++++-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   3 +
 doc/guides/tools/graph.rst |  38 +++++
 6 files changed, 470 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h
  

Comments

Nithin Dabilpuram Oct. 23, 2023, 7:03 a.m. UTC | #1
Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Oct 20, 2023 at 3:47 AM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> Adds framework to initiate a telnet session with application.
>
> Some configurations and debug commands are exposed as runtime APIs.
> Those commands can be invoked using telnet session.
>
> Application initiates a telnet server with host address 0.0.0.0
> and port number 8086 by default.
>
> To make it configurable, "-h" and "-p" options are provided.
> Using them user can pass host address and port number on which
> application will start telnet server.
>
> Using same host address and port number, telnet client can connect
> to application.
>
> Syntax to connect with application:
>         # telnet <host> <port>
>
> Once session is connected, "graph> " prompt will be available.
> Example:
>         # telnet 10.28.35.207 50000
>           Trying 10.28.35.207...
>           Connected to 10.28.35.207.
>           Escape character is '^]'.
>
>           Welcome!
>
>           graph>
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Acked-by: Jerin Jacob <jerinj@marvell.com>
> ---
>  app/graph/conn.c           | 284 +++++++++++++++++++++++++++++++++++++
>  app/graph/conn.h           |  46 ++++++
>  app/graph/main.c           | 103 +++++++++++++-
>  app/graph/meson.build      |   1 +
>  app/graph/module_api.h     |   3 +
>  doc/guides/tools/graph.rst |  38 +++++
>  6 files changed, 470 insertions(+), 5 deletions(-)
>  create mode 100644 app/graph/conn.c
>  create mode 100644 app/graph/conn.h
>
> diff --git a/app/graph/conn.c b/app/graph/conn.c
> new file mode 100644
> index 0000000000..44934602c7
> --- /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 <rte_string_fns.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;
> +       int reuse = 1;
> +
> +       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;
> +       }
> +
> +       if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
> +                      sizeof(reuse)) < 0)
> +               goto free;
> +
> +       rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
> +       if (rc == -1)
> +               goto free;
> +
> +       rc = listen(fd_server, 16);
> +       if (rc == -1)
> +               goto free;
> +
> +       /* Client group */
> +       fd_client_group = epoll_create(1);
> +       if (fd_client_group == -1)
> +               goto free;
> +
> +       /* Fill in */
> +       rte_strscpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
> +       rte_strscpy(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;
> +free:
> +       conn_free(conn);
> +       close(fd_server);
> +       conn = NULL;
> +       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/main.c b/app/graph/main.c
> index 734a94444e..96548f49e7 100644
> --- a/app/graph/main.c
> +++ b/app/graph/main.c
> @@ -2,6 +2,7 @@
>   * Copyright(c) 2023 Marvell.
>   */
>
> +#include <errno.h>
>  #include <fcntl.h>
>  #include <getopt.h>
>  #include <signal.h>
> @@ -11,19 +12,33 @@
>  #include <sys/select.h>
>  #include <unistd.h>
>
> +#include <rte_cycles.h>
>  #include <rte_eal.h>
>  #include <rte_launch.h>
>
>  #include "module_api.h"
>
>  volatile bool force_quit;
> +struct conn *conn;
>
> -static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
> +static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
>                             "[--help]\n";
>
>  static struct app_params {
> +       struct conn_params conn;
>         char *script_name;
>  } 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,
>  };
>
> @@ -42,7 +57,7 @@ app_args_parse(int argc, char **argv)
>         struct option lgopts[] = {
>                 {"help", 0, 0, 'H'},
>         };
> -       int s_present, n_args, i;
> +       int h_present, p_present, s_present, n_args, i;
>         char *app_name = argv[0];
>         int opt, option_index;
>
> @@ -59,10 +74,46 @@ app_args_parse(int argc, char **argv)
>                 return 0;
>
>         /* Parse args */
> +       h_present = 0;
> +       p_present = 0;
>         s_present = 0;
>
> -       while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
> +       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)strtoul(optarg, NULL, 10);
> +                       break;
> +
>                 case 's':
>                         if (s_present) {
>                                 printf("Error: Multiple -s arguments\n");
> @@ -93,6 +144,26 @@ app_args_parse(int argc, char **argv)
>         return 0;
>  }
>
> +bool
> +app_graph_exit(void)
> +{
> +       struct timeval tv;
> +       fd_set fds;
> +       int ret;
> +       char c;
> +
> +       FD_ZERO(&fds);
> +       FD_SET(0, &fds);
> +       tv.tv_sec = 0;
> +       tv.tv_usec = 100;
> +       ret = select(1, &fds, NULL, NULL, &tv);
> +       if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
> +               return true;
> +       else
> +               return false;
> +
> +}
> +
>  int
>  main(int argc, char **argv)
>  {
> @@ -118,10 +189,32 @@ main(int argc, char **argv)
>
>         /* Script */
>         if (app.script_name) {
> -               cli_script_process(app.script_name, 0,
> -                       0, NULL);
> +               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\n");
> +               goto exit;
> +       };
> +
> +       rte_delay_ms(1);
> +       printf("Press enter to exit\n");
> +
> +       /* Dispatch loop */
> +       while (!force_quit) {
> +               conn_req_poll(conn);
> +
> +               conn_msg_poll(conn);
> +               if (app_graph_exit())
> +                       force_quit = true;
>         }
>
> +exit:
> +       conn_free(conn);
>         cli_exit();
>         rte_eal_cleanup();
>         return 0;
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index ed33a04476..c8d2b41b69 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -11,5 +11,6 @@ endif
>  deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
>  sources = files(
>          'cli.c',
> +        'conn.c',
>          'main.c',
>  )
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index 372aeae7e3..9826303f0c 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -7,10 +7,13 @@
>
>  #include <stdint.h>
>  #include <stdbool.h>
> +
>  #include "cli.h"
> +#include "conn.h"
>  /*
>   * Externs
>   */
>  extern volatile bool force_quit;
>
> +bool app_graph_exit(void);
>  #endif
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index cb005e7856..943f915049 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -39,6 +39,16 @@ 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
> @@ -67,7 +77,35 @@ file to express the requested use case configuration.
>  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``
> +by default.
> +
> +if user passes ``-h`` and ``-p`` options while running application then corresponding
> +IP address and port number will be used for telnet session.
> +
> +After successful launch of application, client can connect to application using given
> +host & port and console will be accessed with prompt ``graph>``.
> +
> +Command to access a telnet session
> +
> +.. code-block:: console
> +
> +   telnet <host> <port>
> +
> +Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
> +
> +.. code-block:: console
> +
> +   $ telnet 10.28.35.207 50000
> +   Trying 10.28.35.207...
> +   Connected to 10.28.35.207.
> +   Escape character is '^]'.
> +
> +   Welcome!
>
> +   graph>
> +   graph>
>  Created graph for use case
>  --------------------------
>
> --
> 2.25.1
>
  

Patch

diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..44934602c7
--- /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 <rte_string_fns.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;
+	int reuse = 1;
+
+	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;
+	}
+
+	if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
+		       sizeof(reuse)) < 0)
+		goto free;
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1)
+		goto free;
+
+	rc = listen(fd_server, 16);
+	if (rc == -1)
+		goto free;
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1)
+		goto free;
+
+	/* Fill in */
+	rte_strscpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	rte_strscpy(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;
+free:
+	conn_free(conn);
+	close(fd_server);
+	conn = NULL;
+	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/main.c b/app/graph/main.c
index 734a94444e..96548f49e7 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -2,6 +2,7 @@ 
  * Copyright(c) 2023 Marvell.
  */
 
+#include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <signal.h>
@@ -11,19 +12,33 @@ 
 #include <sys/select.h>
 #include <unistd.h>
 
+#include <rte_cycles.h>
 #include <rte_eal.h>
 #include <rte_launch.h>
 
 #include "module_api.h"
 
 volatile bool force_quit;
+struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
 			    "[--help]\n";
 
 static struct app_params {
+	struct conn_params conn;
 	char *script_name;
 } 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,
 };
 
@@ -42,7 +57,7 @@  app_args_parse(int argc, char **argv)
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
 	};
-	int s_present, n_args, i;
+	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
 	int opt, option_index;
 
@@ -59,10 +74,46 @@  app_args_parse(int argc, char **argv)
 		return 0;
 
 	/* Parse args */
+	h_present = 0;
+	p_present = 0;
 	s_present = 0;
 
-	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+	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)strtoul(optarg, NULL, 10);
+			break;
+
 		case 's':
 			if (s_present) {
 				printf("Error: Multiple -s arguments\n");
@@ -93,6 +144,26 @@  app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_exit(void)
+{
+	struct timeval tv;
+	fd_set fds;
+	int ret;
+	char c;
+
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	tv.tv_sec = 0;
+	tv.tv_usec = 100;
+	ret = select(1, &fds, NULL, NULL, &tv);
+	if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
+		return true;
+	else
+		return false;
+
+}
+
 int
 main(int argc, char **argv)
 {
@@ -118,10 +189,32 @@  main(int argc, char **argv)
 
 	/* Script */
 	if (app.script_name) {
-		cli_script_process(app.script_name, 0,
-			0, NULL);
+		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\n");
+		goto exit;
+	};
+
+	rte_delay_ms(1);
+	printf("Press enter to exit\n");
+
+	/* Dispatch loop */
+	while (!force_quit) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
+		if (app_graph_exit())
+			force_quit = true;
 	}
 
+exit:
+	conn_free(conn);
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index ed33a04476..c8d2b41b69 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,5 +11,6 @@  endif
 deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
+        'conn.c',
         'main.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 372aeae7e3..9826303f0c 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -7,10 +7,13 @@ 
 
 #include <stdint.h>
 #include <stdbool.h>
+
 #include "cli.h"
+#include "conn.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
 
+bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index cb005e7856..943f915049 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -39,6 +39,16 @@  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
@@ -67,7 +77,35 @@  file to express the requested use case configuration.
 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``
+by default.
+
+if user passes ``-h`` and ``-p`` options while running application then corresponding
+IP address and port number will be used for telnet session.
+
+After successful launch of application, client can connect to application using given
+host & port and console will be accessed with prompt ``graph>``.
+
+Command to access a telnet session
+
+.. code-block:: console
+
+   telnet <host> <port>
+
+Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
+
+.. code-block:: console
+
+   $ telnet 10.28.35.207 50000
+   Trying 10.28.35.207...
+   Connected to 10.28.35.207.
+   Escape character is '^]'.
+
+   Welcome!
 
+   graph>
+   graph>
 Created graph for use case
 --------------------------