Acked-By: Nithin Dabilpuram <ndabilpu...@marvell.com>
On Fri, Oct 20, 2023 at 3:47 AM <sk...@marvell.com> wrote: > > From: Sunil Kumar Kori <sk...@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 <sk...@marvell.com> > Signed-off-by: Rakesh Kudurumalla <rkuduruma...@marvell.com> > Acked-by: Jerin Jacob <jer...@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 >