From aad20988b6a0a5058e520948d2a5835cfbc3b523 Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Tue, 20 Jun 2017 16:35:29 +0200 Subject: Rewrite vppctl in C - removes python dependency - removes vpp_api_test dependency - communicates over unix socket - properly detects terminal size and type - responds on terminal resize Change-Id: I46c0a49f9b5f9ef8a0a31faec4fc5d49aa3ee02e Signed-off-by: Damjan Marion --- src/vpp/app/vppctl.c | 322 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 src/vpp/app/vppctl.c (limited to 'src/vpp/app/vppctl.c') diff --git a/src/vpp/app/vppctl.c b/src/vpp/app/vppctl.c new file mode 100644 index 00000000..a8f3eab0 --- /dev/null +++ b/src/vpp/app/vppctl.c @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG 0 + +#if DEBUG +#define TELCMDS +#define TELOPTS +#endif + +#include + +#include +#include +#include + +#define SOCKET_FILE "/run/vpp/cli.sock" + +volatile int window_resized = 0; +struct termios orig_tio; + +static void +send_ttype (clib_socket_t * s, int is_dumb) +{ + clib_socket_tx_add_formatted (s, "%c%c%c" "%c%s" "%c%c", + IAC, SB, TELOPT_TTYPE, + 0, is_dumb ? "dumb" : getenv ("TERM"), + IAC, SE); + clib_socket_tx (s); +} + +static void +send_naws (clib_socket_t * s) +{ + struct winsize ws; + + if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0) + { + clib_unix_warning ("ioctl(TIOCGWINSZ)"); + return; + } + + clib_socket_tx_add_formatted (s, "%c%c%c" "%c%c%c%c" "%c%c", + IAC, SB, TELOPT_NAWS, + ws.ws_col >> 8, ws.ws_col & 0xff, + ws.ws_row >> 8, ws.ws_row & 0xff, IAC, SE); + clib_socket_tx (s); +} + +static void +signal_handler_winch (int signum) +{ + window_resized = 1; +} + +static void +signal_handler_term (int signum) +{ + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio); +} + +static u8 * +process_input (u8 * str, clib_socket_t * s, int is_interactive) +{ + int i = 0; + + while (i < vec_len (s->rx_buffer)) + { + if (s->rx_buffer[i] == IAC) + { + if (s->rx_buffer[i + 1] == SB) + { + u8 *sb = 0; + char opt = s->rx_buffer[i + 2]; + i += 3; + while (s->rx_buffer[i] != IAC) + vec_add1 (sb, s->rx_buffer[i++]); + +#if DEBUG + clib_warning ("SB %s\n %U", TELOPT (opt), + format_hexdump, sb, vec_len (sb)); +#endif + vec_free (sb); + i += 2; + if (opt == TELOPT_TTYPE) + send_ttype (s, !is_interactive); + else if (is_interactive && opt == TELOPT_NAWS) + send_naws (s); + } + else + { +#if DEBUG + clib_warning ("IAC at %d, IAC %s %s", i, + TELCMD (s->rx_buffer[i + 1]), + TELOPT (s->rx_buffer[i + 2])); +#endif + i += 3; + } + } + else + vec_add1 (str, s->rx_buffer[i++]); + } + vec_reset_length (s->rx_buffer); + return str; +} + + +int +main (int argc, char *argv[]) +{ + clib_socket_t _s = { 0 }, *s = &_s; + clib_error_t *error = 0; + struct epoll_event event; + struct sigaction sa; + struct termios tio; + int efd = -1; + u8 *str = 0; + u8 *cmd = 0; + int do_quit = 0; + + + clib_mem_init (0, 64ULL << 10); + + /* process command line */ + argc--; + argv++; + + if (argc > 1 && strcmp (argv[0], "-s") == 0) + { + s->config = argv[1]; + argc -= 2; + argv += 2; + } + else + s->config = SOCKET_FILE; + + while (argc--) + cmd = format (cmd, "%s%c", (argv++)[0], argc ? ' ' : 0); + + s->flags = SOCKET_IS_CLIENT; + + error = clib_socket_init (s); + if (error) + goto done; + + /* Capture terminal resize events */ + memset (&sa, 0, sizeof (struct sigaction)); + sa.sa_handler = signal_handler_winch; + + if (sigaction (SIGWINCH, &sa, 0) < 0) + { + error = clib_error_return_unix (0, "sigaction"); + goto done; + } + + sa.sa_handler = signal_handler_term; + if (sigaction (SIGTERM, &sa, 0) < 0) + { + error = clib_error_return_unix (0, "sigaction"); + goto done; + } + + /* Save the original tty state so we can restore it later */ + tcgetattr (STDIN_FILENO, &orig_tio); + + /* Tweak the tty settings */ + tio = orig_tio; + /* echo off, canonical mode off, ext'd input processing off */ + tio.c_lflag &= ~(ECHO | ICANON | IEXTEN); + tio.c_cc[VMIN] = 1; /* 1 byte at a time */ + tio.c_cc[VTIME] = 0; /* no timer */ + tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio); + + efd = epoll_create1 (0); + + /* register STDIN */ + event.events = EPOLLIN | EPOLLPRI | EPOLLERR; + event.data.fd = STDIN_FILENO; + if (epoll_ctl (efd, EPOLL_CTL_ADD, STDIN_FILENO, &event) != 0) + { + error = clib_error_return_unix (0, "epoll_ctl[%d]", STDIN_FILENO); + goto done; + } + + /* register socket */ + event.events = EPOLLIN | EPOLLPRI | EPOLLERR; + event.data.fd = s->fd; + if (epoll_ctl (efd, EPOLL_CTL_ADD, s->fd, &event) != 0) + { + error = clib_error_return_unix (0, "epoll_ctl[%d]", s->fd); + goto done; + } + + while (1) + { + int n; + + if (window_resized) + { + window_resized = 0; + send_naws (s); + } + + if ((n = epoll_wait (efd, &event, 1, -1)) < 0) + { + /* maybe we received signal */ + if (errno == EINTR) + continue; + + error = clib_error_return_unix (0, "epoll_wait"); + goto done; + } + + if (n == 0) + continue; + + if (event.data.fd == STDIN_FILENO) + { + int n; + char c[100]; + + n = read (STDIN_FILENO, c, sizeof (c)); + if (n > 0) + { + memcpy (clib_socket_tx_add (s, n), c, n); + error = clib_socket_tx (s); + if (error) + goto done; + } + else if (n < 0) + clib_warning ("read rv=%d", n); + } + else if (event.data.fd == s->fd) + { + error = clib_socket_rx (s, 100); + if (error) + break; + + if (clib_socket_rx_end_of_file (s)) + break; + + str = process_input (str, s, cmd == 0); + + if (vec_len (str) > 0) + { + n = write (STDOUT_FILENO, str, vec_len (str)); + if (n < 0) + { + error = clib_error_return_unix (0, "write"); + goto done; + } + vec_reset_length (str); + } + + if (do_quit) + { + clib_socket_tx_add_formatted (s, "q\n"); + clib_socket_tx (s); + do_quit = 0; + } + if (cmd) + { + clib_socket_tx_add_formatted (s, "%s\n", cmd); + clib_socket_tx (s); + vec_free (cmd); + do_quit = 1; + } + } + else + { + error = clib_error_return (0, "unknown fd"); + goto done; + } + } + + error = clib_socket_close (s); + +done: + vec_free (cmd); + vec_free (str); + if (efd > -1) + close (efd); + + if (error) + { + clib_error_report (error); + return 1; + } + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio); + return 0; +} + +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ -- cgit 1.2.3-korg From f49921f73f4e1f0b67823be445aafeb9ff2333a6 Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Mon, 11 Sep 2017 16:52:11 +0200 Subject: clib_socket: add sendmsg / recvmsg with ancillary data support Change-Id: Ie18580e05ec12291e7026f21ad874e088a712c8e Signed-off-by: Damjan Marion --- src/vlib/unix/cli.c | 4 +- src/vpp/app/vppctl.c | 2 +- src/vppinfra/socket.c | 130 ++++++++++++++++++++++++++++++++++++++++----- src/vppinfra/socket.h | 49 +++++++++++++---- src/vppinfra/test_socket.c | 6 +-- 5 files changed, 164 insertions(+), 27 deletions(-) (limited to 'src/vpp/app/vppctl.c') diff --git a/src/vlib/unix/cli.c b/src/vlib/unix/cli.c index 39368823..1567cc2a 100644 --- a/src/vlib/unix/cli.c +++ b/src/vlib/unix/cli.c @@ -2664,8 +2664,8 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) vec_free (tmp); } - s->flags = SOCKET_IS_SERVER | /* listen, don't connect */ - SOCKET_ALLOW_GROUP_WRITE; /* PF_LOCAL socket only */ + s->flags = CLIB_SOCKET_F_IS_SERVER | /* listen, don't connect */ + CLIB_SOCKET_F_ALLOW_GROUP_WRITE; /* PF_LOCAL socket only */ error = clib_socket_init (s); if (error) diff --git a/src/vpp/app/vppctl.c b/src/vpp/app/vppctl.c index a8f3eab0..980936f1 100644 --- a/src/vpp/app/vppctl.c +++ b/src/vpp/app/vppctl.c @@ -158,7 +158,7 @@ main (int argc, char *argv[]) while (argc--) cmd = format (cmd, "%s%c", (argv++)[0], argc ? ' ' : 0); - s->flags = SOCKET_IS_CLIENT; + s->flags = CLIB_SOCKET_F_IS_CLIENT; error = clib_socket_init (s); if (error) diff --git a/src/vppinfra/socket.c b/src/vppinfra/socket.c index 37dcbbfd..87a9333f 100644 --- a/src/vppinfra/socket.c +++ b/src/vppinfra/socket.c @@ -35,17 +35,18 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include +#include /* strchr */ +#define __USE_GNU #include #include +#include #include #include #include #include #include -#include #include -#include /* strchr */ #include #include @@ -233,7 +234,7 @@ default_socket_read (clib_socket_t * sock, int n_bytes) u8 *buf; /* RX side of socket is down once end of file is reached. */ - if (sock->flags & SOCKET_RX_END_OF_FILE) + if (sock->flags & CLIB_SOCKET_F_RX_END_OF_FILE) return 0; fd = sock->fd; @@ -255,7 +256,7 @@ default_socket_read (clib_socket_t * sock, int n_bytes) /* Other side closed the socket. */ if (n_read == 0) - sock->flags |= SOCKET_RX_END_OF_FILE; + sock->flags |= CLIB_SOCKET_F_RX_END_OF_FILE; non_fatal: _vec_len (sock->rx_buffer) += n_read - n_bytes; @@ -271,6 +272,91 @@ default_socket_close (clib_socket_t * s) return 0; } +static clib_error_t * +default_socket_sendmsg (clib_socket_t * s, void *msg, int msglen, + int fds[], int num_fds) +{ + struct msghdr mh = { 0 }; + struct iovec iov[1]; + char ctl[CMSG_SPACE (sizeof (int)) * num_fds]; + int rv; + + iov[0].iov_base = msg; + iov[0].iov_len = msglen; + mh.msg_iov = iov; + mh.msg_iovlen = 1; + + if (num_fds > 0) + { + struct cmsghdr *cmsg; + memset (&ctl, 0, sizeof (ctl)); + mh.msg_control = ctl; + mh.msg_controllen = sizeof (ctl); + cmsg = CMSG_FIRSTHDR (&mh); + cmsg->cmsg_len = CMSG_LEN (sizeof (int) * num_fds); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + memcpy (CMSG_DATA (cmsg), fds, sizeof (int) * num_fds); + } + rv = sendmsg (s->fd, &mh, 0); + if (rv < 0) + return clib_error_return_unix (0, "sendmsg"); + return 0; +} + + +static clib_error_t * +default_socket_recvmsg (clib_socket_t * s, void *msg, int msglen, + int fds[], int num_fds) +{ + char ctl[CMSG_SPACE (sizeof (int) * num_fds) + + CMSG_SPACE (sizeof (struct ucred))]; + struct msghdr mh = { 0 }; + struct iovec iov[1]; + ssize_t size; + struct ucred *cr = 0; + struct cmsghdr *cmsg; + + iov[0].iov_base = msg; + iov[0].iov_len = msglen; + mh.msg_iov = iov; + mh.msg_iovlen = 1; + mh.msg_control = ctl; + mh.msg_controllen = sizeof (ctl); + + memset (ctl, 0, sizeof (ctl)); + + /* receive the incoming message */ + size = recvmsg (s->fd, &mh, 0); + if (size != msglen) + { + return (size == 0) ? clib_error_return (0, "disconnected") : + clib_error_return_unix (0, "recvmsg: malformed message (fd %d, '%s')", + s->fd, s->config); + } + + cmsg = CMSG_FIRSTHDR (&mh); + while (cmsg) + { + if (cmsg->cmsg_level == SOL_SOCKET) + { + if (cmsg->cmsg_type == SCM_CREDENTIALS) + { + cr = (struct ucred *) CMSG_DATA (cmsg); + s->uid = cr->uid; + s->gid = cr->gid; + s->pid = cr->pid; + } + else if (cmsg->cmsg_type == SCM_RIGHTS) + { + clib_memcpy (fds, CMSG_DATA (cmsg), num_fds * sizeof (int)); + } + } + cmsg = CMSG_NXTHDR (&mh, cmsg); + } + return 0; +} + static void socket_init_funcs (clib_socket_t * s) { @@ -280,6 +366,10 @@ socket_init_funcs (clib_socket_t * s) s->read_func = default_socket_read; if (!s->close_func) s->close_func = default_socket_close; + if (!s->sendmsg_func) + s->sendmsg_func = default_socket_sendmsg; + if (!s->recvmsg_func) + s->recvmsg_func = default_socket_recvmsg; } clib_error_t * @@ -291,18 +381,22 @@ clib_socket_init (clib_socket_t * s) struct sockaddr_un su; } addr; socklen_t addr_len = 0; + int socket_type; clib_error_t *error = 0; word port; error = socket_config (s->config, &addr.sa, &addr_len, - (s->flags & SOCKET_IS_SERVER + (s->flags & CLIB_SOCKET_F_IS_SERVER ? INADDR_LOOPBACK : INADDR_ANY)); if (error) goto done; socket_init_funcs (s); - s->fd = socket (addr.sa.sa_family, SOCK_STREAM, 0); + socket_type = s->flags & CLIB_SOCKET_F_SEQPACKET ? + SOCK_SEQPACKET : SOCK_STREAM; + + s->fd = socket (addr.sa.sa_family, socket_type, 0); if (s->fd < 0) { error = clib_error_return_unix (0, "socket (fd %d, '%s')", @@ -314,7 +408,7 @@ clib_socket_init (clib_socket_t * s) if (addr.sa.sa_family == PF_INET) port = ((struct sockaddr_in *) &addr)->sin_port; - if (s->flags & SOCKET_IS_SERVER) + if (s->flags & CLIB_SOCKET_F_IS_SERVER) { uword need_bind = 1; @@ -342,6 +436,18 @@ clib_socket_init (clib_socket_t * s) clib_unix_warning ("setsockopt SO_REUSEADDR fails"); } + if (addr.sa.sa_family == PF_LOCAL && s->flags & CLIB_SOCKET_F_PASSCRED) + { + int x = 1; + if (setsockopt (s->fd, SOL_SOCKET, SO_PASSCRED, &x, sizeof (x)) < 0) + { + error = clib_error_return_unix (0, "setsockopt (SO_PASSCRED, " + "fd %d, '%s')", s->fd, + s->config); + goto done; + } + } + if (need_bind && bind (s->fd, &addr.sa, addr_len) < 0) { error = clib_error_return_unix (0, "bind (fd %d, '%s')", @@ -356,7 +462,7 @@ clib_socket_init (clib_socket_t * s) goto done; } if (addr.sa.sa_family == PF_LOCAL - && s->flags & SOCKET_ALLOW_GROUP_WRITE) + && s->flags & CLIB_SOCKET_F_ALLOW_GROUP_WRITE) { struct stat st = { 0 }; if (stat (((struct sockaddr_un *) &addr)->sun_path, &st) < 0) @@ -378,7 +484,7 @@ clib_socket_init (clib_socket_t * s) } else { - if ((s->flags & SOCKET_NON_BLOCKING_CONNECT) + if ((s->flags & CLIB_SOCKET_F_NON_BLOCKING_CONNECT) && fcntl (s->fd, F_SETFL, O_NONBLOCK) < 0) { error = clib_error_return_unix (0, "fcntl NONBLOCK (fd %d, '%s')", @@ -387,7 +493,7 @@ clib_socket_init (clib_socket_t * s) } if (connect (s->fd, &addr.sa, addr_len) < 0 - && !((s->flags & SOCKET_NON_BLOCKING_CONNECT) && + && !((s->flags & CLIB_SOCKET_F_NON_BLOCKING_CONNECT) && errno == EINPROGRESS)) { error = clib_error_return_unix (0, "connect (fd %d, '%s')", @@ -434,7 +540,7 @@ clib_socket_accept (clib_socket_t * server, clib_socket_t * client) goto close_client; } - client->flags = SOCKET_IS_CLIENT; + client->flags = CLIB_SOCKET_F_IS_CLIENT; socket_init_funcs (client); return 0; diff --git a/src/vppinfra/socket.h b/src/vppinfra/socket.h index 75037208..4f9e9509 100644 --- a/src/vppinfra/socket.h +++ b/src/vppinfra/socket.h @@ -55,13 +55,14 @@ typedef struct _socket_t char *config; u32 flags; -#define SOCKET_IS_SERVER (1 << 0) -#define SOCKET_IS_CLIENT (0 << 0) -#define SOCKET_NON_BLOCKING_CONNECT (1 << 1) -#define SOCKET_ALLOW_GROUP_WRITE (1 << 2) +#define CLIB_SOCKET_F_IS_SERVER (1 << 0) +#define CLIB_SOCKET_F_IS_CLIENT (0 << 0) +#define CLIB_SOCKET_F_RX_END_OF_FILE (1 << 2) +#define CLIB_SOCKET_F_NON_BLOCKING_CONNECT (1 << 3) +#define CLIB_SOCKET_F_ALLOW_GROUP_WRITE (1 << 4) +#define CLIB_SOCKET_F_SEQPACKET (1 << 5) +#define CLIB_SOCKET_F_PASSCRED (1 << 6) - /* Read returned end-of-file. */ -#define SOCKET_RX_END_OF_FILE (1 << 2) /* Transmit buffer. Holds data waiting to be written. */ u8 *tx_buffer; @@ -72,10 +73,19 @@ typedef struct _socket_t /* Peer socket we are connected to. */ struct sockaddr_in peer; + /* Credentials, populated if CLIB_SOCKET_F_PASSCRED is set */ + pid_t pid; + uid_t uid; + gid_t gid; + clib_error_t *(*write_func) (struct _socket_t * sock); clib_error_t *(*read_func) (struct _socket_t * sock, int min_bytes); clib_error_t *(*close_func) (struct _socket_t * sock); - void *private_data; + clib_error_t *(*recvmsg_func) (struct _socket_t * s, void *msg, int msglen, + int fds[], int num_fds); + clib_error_t *(*sendmsg_func) (struct _socket_t * s, void *msg, int msglen, + int fds[], int num_fds); + uword private_data; } clib_socket_t; /* socket config format is host:port. @@ -89,7 +99,7 @@ clib_error_t *clib_socket_accept (clib_socket_t * server, always_inline uword clib_socket_is_server (clib_socket_t * sock) { - return (sock->flags & SOCKET_IS_SERVER) != 0; + return (sock->flags & CLIB_SOCKET_F_IS_SERVER) != 0; } always_inline uword @@ -98,10 +108,17 @@ clib_socket_is_client (clib_socket_t * s) return !clib_socket_is_server (s); } +always_inline uword +clib_socket_is_connected (clib_socket_t * sock) +{ + return sock->fd > 0; +} + + always_inline int clib_socket_rx_end_of_file (clib_socket_t * s) { - return s->flags & SOCKET_RX_END_OF_FILE; + return s->flags & CLIB_SOCKET_F_RX_END_OF_FILE; } always_inline void * @@ -130,6 +147,20 @@ clib_socket_rx (clib_socket_t * s, int n_bytes) return s->read_func (s, n_bytes); } +always_inline clib_error_t * +clib_socket_sendmsg (clib_socket_t * s, void *msg, int msglen, + int fds[], int num_fds) +{ + return s->sendmsg_func (s, msg, msglen, fds, num_fds); +} + +always_inline clib_error_t * +clib_socket_recvmsg (clib_socket_t * s, void *msg, int msglen, + int fds[], int num_fds) +{ + return s->recvmsg_func (s, msg, msglen, fds, num_fds); +} + always_inline void clib_socket_free (clib_socket_t * s) { diff --git a/src/vppinfra/test_socket.c b/src/vppinfra/test_socket.c index 0b05467a..2f25eccd 100644 --- a/src/vppinfra/test_socket.c +++ b/src/vppinfra/test_socket.c @@ -50,15 +50,15 @@ test_socket_main (unformat_input_t * input) clib_error_t *error; s->config = "localhost:22"; - s->flags = SOCKET_IS_CLIENT; + s->flags = CLIB_SOCKET_F_IS_CLIENT; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "server %s %=", &config, - &s->flags, SOCKET_IS_SERVER)) + &s->flags, CLIB_SOCKET_F_IS_SERVER)) ; else if (unformat (input, "client %s %=", &config, - &s->flags, SOCKET_IS_CLIENT)) + &s->flags, CLIB_SOCKET_F_IS_CLIENT)) ; else { -- cgit 1.2.3-korg From 03add7f5b5e5351790187ea6d7e83803d5be2440 Mon Sep 17 00:00:00 2001 From: Chris Luke Date: Wed, 20 Sep 2017 23:31:24 -0400 Subject: vppctl,cli: Improve non-interactive vppctl (VPP-944) Short version: Make vppctl behave as expected when run from scripts, or without a controlling terminal, and especially when using it with VPP commands on its command line ("non-interactively"). In particular, prevent the welcome banner and VPP CLI prompt from being sent by VPP when being used in these ways. vppctl ------ - Improve vppctl's detection of non-interactive sessions. - Pass non-interactiveness in the terminal type telnet option as a value distinct from "dumb" (which means non-ANSI capable.) - Make tty setup handling more robust. - Only send non-interactive command once we've sent the terminal type, to ensure correct event sequence; we need the VPP cli session to be in line-by-line mode. - Ignore stdin when it looks something like /dev/null. - Skip NUL bytes received from VPP. VPP CLI ------- - Detect "non-interactive" terminal types and set session parameters accordingly. - Add an "interactive" flag that controls whether the welcome banner and CLI prompt are sent. - Detect if telnet options processing switched us into line mode and act accordingly for the rest of the current input buffer. This was causing the command string to be echoed by the CLI editor code. - For non-interactive sessions, send a NUL byte after the input buffer has been processed. This is because vppctl depends on seeing traffic before it will try to close the session; a command with no output would cause it to hang. NUL bytes are ignored by all decent terminals, but we have vppctl strip them out anyway. - Prevent certain commands from running in non-interactive sessions since they manipulate interactive-related features. - For interactive sessions, quench the prompt that prints on VPP shutdown. - Detect and handle socket errors in the CLI; sessions were leaking. - Pevent SIGPIPE from ever being raised; handle EPIPE instead. We don't need VPP to die just because a socket closed just before we try to write to it! - Add a command to dump a list of current CLI sessions; mostly this was to detect session leakage, but it may have some general utility. Change-Id: Ia147da013317180882c1d967b18eefb8519a55fb Signed-off-by: Chris Luke --- src/vlib/unix/cli.c | 359 +++++++++++++++++++++++++++++++++++++++++++-------- src/vpp/app/vppctl.c | 134 +++++++++++++------ 2 files changed, 404 insertions(+), 89 deletions(-) (limited to 'src/vpp/app/vppctl.c') diff --git a/src/vlib/unix/cli.c b/src/vlib/unix/cli.c index 0e035b13..ffe5de6d 100644 --- a/src/vlib/unix/cli.c +++ b/src/vlib/unix/cli.c @@ -100,9 +100,6 @@ /** Maximum terminal height we will accept */ #define UNIX_CLI_MAX_TERMINAL_HEIGHT 512 -/** Unix standard in */ -#define UNIX_CLI_STDIN_FD 0 - /** A CLI banner line. */ typedef struct @@ -200,6 +197,18 @@ typedef struct /** Disable the pager? */ u8 no_pager; + /** Whether the session is interactive or not. + * Controls things like initial banner, the CLI prompt etc. */ + u8 is_interactive; + + /** Whether the session is attached to a socket. */ + u8 is_socket; + + /** If EPIPE has been detected, prevent further write-related + * activity on the descriptor. + */ + u8 has_epipe; + /** Pager buffer */ u8 **pager_vector; @@ -593,12 +602,33 @@ unix_vlib_cli_output_raw (unix_cli_file_t * cf, { int n = 0; + if (cf->has_epipe) /* don't try writing anything */ + return; + if (vec_len (cf->output_vector) == 0) - n = write (uf->file_descriptor, buffer, buffer_bytes); + { + if (cf->is_socket) + /* If it's a socket we use MSG_NOSIGNAL to prevent SIGPIPE */ + n = send (uf->file_descriptor, buffer, buffer_bytes, MSG_NOSIGNAL); + else + n = write (uf->file_descriptor, buffer, buffer_bytes); + } if (n < 0 && errno != EAGAIN) { - clib_unix_warning ("write"); + if (errno == EPIPE) + { + /* connection closed on us */ + unix_main_t *um = &unix_main; + cf->has_epipe = 1; + vlib_process_signal_event (um->vlib_main, cf->process_node_index, + UNIX_CLI_PROCESS_EVENT_QUIT, + uf->private_data); + } + else + { + clib_unix_warning ("write"); + } } else if ((word) n < (word) buffer_bytes) { @@ -659,7 +689,9 @@ unix_cli_cli_prompt (unix_cli_file_t * cf, clib_file_t * uf) { unix_cli_main_t *cm = &unix_cli_main; - unix_vlib_cli_output_raw (cf, uf, cm->cli_prompt, vec_len (cm->cli_prompt)); + if (cf->is_interactive) /* Only interactive sessions get a prompt */ + unix_vlib_cli_output_raw (cf, uf, cm->cli_prompt, + vec_len (cm->cli_prompt)); } /** @brief Output a pager prompt and show number of buffered lines */ @@ -1024,7 +1056,7 @@ unix_vlib_cli_output (uword cli_file_index, u8 * buffer, uword buffer_bytes) * terminal sequences; @c 0 otherwise. */ static u8 -unix_cli_terminal_type (u8 * term, uword len) +unix_cli_terminal_type_ansi (u8 * term, uword len) { /* This may later be better done as a hash of some sort. */ #define _(a) do { \ @@ -1042,6 +1074,45 @@ unix_cli_terminal_type (u8 * term, uword len) return 0; } +/** Identify whether a terminal type is non-interactive. + * + * Compares the string given in @c term with a list of terminal types known + * to be non-interactive, as send by tools such as @c vppctl . + * + * This list contains, for example, @c vppctl. + * + * @param term A string with a terminal type in it. + * @param len The length of the string in @c term. + * + * @return @c 1 if the terminal type is recognized as being non-interactive; + * @c 0 otherwise. + */ +static u8 +unix_cli_terminal_type_noninteractive (u8 * term, uword len) +{ + /* This may later be better done as a hash of some sort. */ +#define _(a) do { \ + if (strncasecmp(a, (char *)term, (size_t)len) == 0) return 1; \ + } while(0) + + _("vppctl"); +#undef _ + + return 0; +} + +/** Set a session to be non-interactive. */ +static void +unix_cli_set_session_noninteractive (unix_cli_file_t * cf) +{ + /* Non-interactive sessions don't get these */ + cf->is_interactive = 0; + cf->no_pager = 1; + cf->history_limit = 0; + cf->has_history = 0; + cf->line_mode = 1; +} + /** @brief Emit initial welcome banner and prompt on a connection. */ static void unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf) @@ -1052,6 +1123,12 @@ unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf) unix_cli_banner_t *banner; int i, len; + /* Mark the session as started if we get here */ + cf->started = 1; + + if (!(cf->is_interactive)) /* No banner for non-interactive sessions */ + return; + /* * Put the first bytes directly into the buffer so that further output is * queued until everything is ready. (oterwise initial prompt can appear @@ -1082,7 +1159,6 @@ unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf) /* Prompt. */ unix_cli_cli_prompt (cf, uf); - cf->started = 1; } /** @brief A failsafe triggered on a timer to ensure we send the prompt @@ -1160,9 +1236,20 @@ unix_cli_process_telnet (unix_main_t * um, case TELOPT_TTYPE: if (input_vector[3] != 0) break; - /* See if the terminal type is ANSI capable */ - cf->ansi_capable = - unix_cli_terminal_type (input_vector + 4, i - 5); + { + /* See if the the terminal type is recognized */ + u8 *term = input_vector + 4; + uword len = i - 5; + + /* See if the terminal type is ANSI capable */ + cf->ansi_capable = + unix_cli_terminal_type_ansi (term, len); + + /* See if the terminal type indicates non-interactive */ + if (unix_cli_terminal_type_noninteractive (term, len)) + unix_cli_set_session_noninteractive (cf); + } + /* If session not started, we can release the pause */ if (!cf->started) /* Send the welcome banner and initial prompt */ @@ -2122,10 +2209,12 @@ unix_cli_line_edit (unix_cli_main_t * cm, unix_main_t * um, vec_len (cf->input_vector) - i); if (matched < 0) { + /* There was a partial match which means we need more bytes + * than the input buffer currently has. + */ if (i) { - /* There was a partial match which means we need more bytes - * than the input buffer currently has. + /* * Since the bytes before here have been processed, shift * the remaining contents to the start of the input buffer. */ @@ -2136,6 +2225,16 @@ unix_cli_line_edit (unix_cli_main_t * cm, unix_main_t * um, break; default: + /* If telnet option processing switched us to line mode, get us + * out of here! + */ + if (cf->line_mode) + { + vec_delete (cf->input_vector, i, 0); + cf->current_command = cf->input_vector; + return 0; + } + /* process the action */ if (!unix_cli_line_process_one (cm, um, cf, uf, cf->input_vector[i], action)) @@ -2165,11 +2264,14 @@ unix_cli_process_input (unix_cli_main_t * cm, uword cli_file_index) unformat_input_t input; int vlib_parse_eval (u8 *); + cm->current_input_file_index = cli_file_index; + more: /* Try vlibplex first. Someday... */ if (0 && vlib_parse_eval (cf->input_vector) == 0) goto done; + if (cf->line_mode) { /* just treat whatever we got as a complete line of input */ @@ -2190,20 +2292,16 @@ more: lv = format (lv, "%U[%d]: %v", format_timeval, 0 /* current bat-time */ , 0 /* current bat-format */ , - cli_file_index, cf->input_vector); - { - int rv __attribute__ ((unused)) = - write (um->log_fd, lv, vec_len (lv)); - } + cli_file_index, cf->current_command); + int rv __attribute__ ((unused)) = write (um->log_fd, lv, vec_len (lv)); } - /* Copy our input command to a new string */ + /* Build an unformat structure around our command */ unformat_init_vector (&input, cf->current_command); /* Remove leading white space from input. */ (void) unformat (&input, ""); - cm->current_input_file_index = cli_file_index; cf->pager_start = 0; /* start a new pager session */ if (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT) @@ -2222,9 +2320,14 @@ more: done: /* reset vector; we'll re-use it later */ if (cf->line_mode) - vec_reset_length (cf->input_vector); + { + vec_reset_length (cf->input_vector); + cf->current_command = 0; + } else - vec_reset_length (cf->current_command); + { + vec_reset_length (cf->current_command); + } if (cf->no_pager == 2) { @@ -2251,6 +2354,15 @@ done: /* Any residual data in the input vector? */ if (vec_len (cf->input_vector)) goto more; + + /* For non-interactive sessions send a NUL byte. + * Specifically this is because vppctl needs to see some traffic in + * order to move on to closing the session. Commands with no output + * would thus cause vppctl to hang indefinitely in non-interactive mode + * since there is also no prompt sent after the command completes. + */ + if (!cf->is_interactive) + unix_vlib_cli_output_raw (cf, uf, (u8 *) "\0", 1); } /** Destroy a CLI session. @@ -2270,7 +2382,7 @@ unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index) uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); /* Quit/EOF on stdin means quit program. */ - if (uf->file_descriptor == UNIX_CLI_STDIN_FD) + if (uf->file_descriptor == STDIN_FILENO) clib_longjmp (&um->vlib_main->main_loop_exit, VLIB_MAIN_LOOP_EXIT_CLI); vec_free (cf->current_command); @@ -2342,11 +2454,30 @@ unix_cli_write_ready (clib_file_t * uf) cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data); /* Flush output vector. */ - n = write (uf->file_descriptor, - cf->output_vector, vec_len (cf->output_vector)); + if (cf->is_socket) + /* If it's a socket we use MSG_NOSIGNAL to prevent SIGPIPE */ + n = send (uf->file_descriptor, + cf->output_vector, vec_len (cf->output_vector), MSG_NOSIGNAL); + else + n = write (uf->file_descriptor, + cf->output_vector, vec_len (cf->output_vector)); if (n < 0 && errno != EAGAIN) - return clib_error_return_unix (0, "write"); + { + if (errno == EPIPE) + { + /* connection closed on us */ + unix_main_t *um = &unix_main; + cf->has_epipe = 1; + vlib_process_signal_event (um->vlib_main, cf->process_node_index, + UNIX_CLI_PROCESS_EVENT_QUIT, + uf->private_data); + } + else + { + return clib_error_return_unix (0, "write"); + } + } else if (n > 0) unix_cli_del_pending_output (uf, cf, n); @@ -2393,6 +2524,24 @@ unix_cli_read_ready (clib_file_t * uf) return /* no error */ 0; } +/** Called when a CLI session file descriptor has an error condition. */ +static clib_error_t * +unix_cli_error_detected (clib_file_t * uf) +{ + unix_main_t *um = &unix_main; + unix_cli_main_t *cm = &unix_cli_main; + unix_cli_file_t *cf; + + cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data); + cf->has_epipe = 1; /* prevent writes while the close is pending */ + vlib_process_signal_event (um->vlib_main, + cf->process_node_index, + UNIX_CLI_PROCESS_EVENT_QUIT, + /* event data */ uf->private_data); + + return /* no error */ 0; +} + /** Store a new CLI session. * @param name The name of the session. * @param fd The file descriptor for the session I/O. @@ -2443,6 +2592,7 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd) template.read_function = unix_cli_read_ready; template.write_function = unix_cli_write_ready; + template.error_function = unix_cli_error_detected; template.file_descriptor = fd; template.private_data = cf - cm->cli_file_pool; @@ -2482,10 +2632,10 @@ unix_cli_listen_read_ready (clib_file_t * uf) cf_index = unix_cli_file_add (cm, client_name, client.fd); cf = pool_elt_at_index (cm->cli_file_pool, cf_index); + cf->is_socket = 1; /* No longer need CLIB version of socket. */ clib_socket_free (&client); - vec_free (client_name); /* if we're supposed to run telnet session in character mode (default) */ @@ -2512,6 +2662,9 @@ unix_cli_listen_read_ready (clib_file_t * uf) cf->history_limit = um->cli_history_limit; cf->has_history = cf->history_limit != 0; + /* This is an interactive session until we decide otherwise */ + cf->is_interactive = 1; + /* Make sure this session is in line mode */ cf->line_mode = 0; @@ -2521,9 +2674,8 @@ unix_cli_listen_read_ready (clib_file_t * uf) /* Setup the pager */ cf->no_pager = um->cli_no_pager; - uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); - /* Send the telnet options */ + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); unix_vlib_cli_output_raw (cf, uf, charmode_option, ARRAY_LEN (charmode_option)); @@ -2550,7 +2702,7 @@ unix_cli_resize_interrupt (int signum) (void) signum; /* Terminal resized, fetch the new size */ - if (ioctl (UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws) < 0) + if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0) { /* "Should never happen..." */ clib_unix_warning ("TIOCGWINSZ"); @@ -2600,17 +2752,17 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) if (um->flags & UNIX_FLAG_INTERACTIVE) { /* Set stdin to be non-blocking. */ - if ((flags = fcntl (UNIX_CLI_STDIN_FD, F_GETFL, 0)) < 0) + if ((flags = fcntl (STDIN_FILENO, F_GETFL, 0)) < 0) flags = 0; - (void) fcntl (UNIX_CLI_STDIN_FD, F_SETFL, flags | O_NONBLOCK); + (void) fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); - cf_index = unix_cli_file_add (cm, "stdin", UNIX_CLI_STDIN_FD); + cf_index = unix_cli_file_add (cm, "stdin", STDIN_FILENO); cf = pool_elt_at_index (cm->cli_file_pool, cf_index); cm->stdin_cli_file_index = cf_index; /* If stdin is a tty and we are using chacracter mode, enable * history on the CLI and set the tty line discipline accordingly. */ - if (isatty (UNIX_CLI_STDIN_FD) && um->cli_line_mode == 0) + if (isatty (STDIN_FILENO) && um->cli_line_mode == 0) { /* Capture terminal resize events */ memset (&sa, 0, sizeof (sa)); @@ -2619,7 +2771,7 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) clib_panic ("sigaction"); /* Retrieve the current terminal size */ - ioctl (UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws); + ioctl (STDIN_FILENO, TIOCGWINSZ, &ws); cf->width = ws.ws_col; cf->height = ws.ws_row; @@ -2634,11 +2786,14 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) /* Setup the pager */ cf->no_pager = um->cli_no_pager; + /* This is an interactive session until we decide otherwise */ + cf->is_interactive = 1; + /* We're going to be in char by char mode */ cf->line_mode = 0; /* Save the original tty state so we can restore it later */ - tcgetattr (UNIX_CLI_STDIN_FD, &um->tio_stdin); + tcgetattr (STDIN_FILENO, &um->tio_stdin); um->tio_isset = 1; /* Tweak the tty settings */ @@ -2647,23 +2802,23 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) tio.c_lflag &= ~(ECHO | ICANON | IEXTEN); tio.c_cc[VMIN] = 1; /* 1 byte at a time */ tio.c_cc[VTIME] = 0; /* no timer */ - tcsetattr (UNIX_CLI_STDIN_FD, TCSAFLUSH, &tio); + tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio); /* See if we can do ANSI/VT100 output */ term = (u8 *) getenv ("TERM"); if (term != NULL) - cf->ansi_capable = unix_cli_terminal_type (term, - strlen ((char *) - term)); + { + int len = strlen ((char *) term); + cf->ansi_capable = unix_cli_terminal_type_ansi (term, len); + if (unix_cli_terminal_type_noninteractive (term, len)) + unix_cli_set_session_noninteractive (cf); + } } else { notty: - /* No tty, so make sure these things are off */ - cf->no_pager = 1; - cf->history_limit = 0; - cf->has_history = 0; - cf->line_mode = 1; + /* No tty, so make sure the session doesn't have tty-like features */ + unix_cli_set_session_noninteractive (cf); } /* Send banner and initial prompt */ @@ -2726,8 +2881,8 @@ unix_cli_exit (vlib_main_t * vm) unix_main_t *um = &unix_main; /* If stdin is a tty and we saved the tty state, reset the tty state */ - if (isatty (UNIX_CLI_STDIN_FD) && um->tio_isset) - tcsetattr (UNIX_CLI_STDIN_FD, TCSAFLUSH, &um->tio_stdin); + if (isatty (STDIN_FILENO) && um->tio_isset) + tcsetattr (STDIN_FILENO, TCSAFLUSH, &um->tio_stdin); return 0; } @@ -2758,6 +2913,12 @@ unix_cli_quit (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { unix_cli_main_t *cm = &unix_cli_main; + unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool, + cm->current_input_file_index); + + /* Cosmetic: suppress the final prompt from appearing before we die */ + cf->is_interactive = 0; + cf->started = 1; vlib_process_signal_event (vm, vlib_current_process (vm), @@ -2940,6 +3101,9 @@ unix_cli_show_history (vlib_main_t * vm, cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + if (cf->has_history && cf->history_limit) { i = 1 + cf->command_number - vec_len (cf->command_history); @@ -2985,6 +3149,8 @@ unix_cli_show_terminal (vlib_main_t * vm, vlib_cli_output (vm, "Terminal height: %d\n", cf->height); vlib_cli_output (vm, "ANSI capable: %s\n", cf->ansi_capable ? "yes" : "no"); + vlib_cli_output (vm, "Interactive: %s\n", + cf->is_interactive ? "yes" : "no"); vlib_cli_output (vm, "History enabled: %s%s\n", cf->has_history ? "yes" : "no", !cf->has_history || cf->history_limit ? "" : @@ -3017,6 +3183,7 @@ unix_cli_show_terminal (vlib_main_t * vm, * Terminal width: 123 * Terminal height: 48 * ANSI capable: yes + * Interactive: yes * History enabled: yes * History limit: 50 * Pager enabled: yes @@ -3032,6 +3199,87 @@ VLIB_CLI_COMMAND (cli_unix_cli_show_terminal, static) = { }; /* *INDENT-ON* */ +/** CLI command to display a list of CLI sessions. */ +static clib_error_t * +unix_cli_show_cli_sessions (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + //unix_main_t *um = &unix_main; + unix_cli_main_t *cm = &unix_cli_main; + clib_file_main_t *fm = &file_main; + unix_cli_file_t *cf; + clib_file_t *uf; + vlib_node_t *n; + + vlib_cli_output (vm, "%-5s %-5s %-20s %s", "PNI", "FD", "Name", "Flags"); + +#define fl(x, y) ( (x) ? toupper((y)) : tolower((y)) ) + /* *INDENT-OFF* */ + pool_foreach (cf, cm->cli_file_pool, ({ + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); + n = vlib_get_node (vm, cf->process_node_index); + vlib_cli_output (vm, + "%-5d %-5d %-20v %c%c%c%c%c\n", + cf->process_node_index, + uf->file_descriptor, + n->name, + fl (cf->is_interactive, 'i'), + fl (cf->is_socket, 's'), + fl (cf->line_mode, 'l'), + fl (cf->has_epipe, 'p'), + fl (cf->ansi_capable, 'a')); + })); + /* *INDENT-ON* */ +#undef fl + + return 0; +} + +/*? + * Displays a summary of all the current CLI sessions. + * + * Typically used to diagnose connection issues with the CLI + * socket. + * + * @cliexpar + * @cliexstart{show cli-sessions} + * PNI FD Name Flags + * 343 0 unix-cli-stdin IslpA + * 344 7 unix-cli-local:20 ISlpA + * 346 8 unix-cli-local:21 iSLpa + * @cliexend + + * In this example we have the debug console of the running process + * on stdin/out, we have an interactive socket session and we also + * have a non-interactive socket session. + * + * Fields: + * + * - @em PNI: Process node index. + * - @em FD: Unix file descriptor. + * - @em Name: Name of the session. + * - @em Flags: Various flags that describe the state of the session. + * + * @em Flags have the following meanings; lower-case typically negates + * upper-case: + * + * - @em I Interactive session. + * - @em S Connected by socket. + * - @em s Not a socket, likely stdin. + * - @em L Line-by-line mode. + * - @em l Char-by-char mode. + * - @em P EPIPE detected on connection; it will close soon. + * - @em A ANSI-capable terminal. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_cli_show_cli_sessions, static) = { + .path = "show cli-sessions", + .short_help = "Show current CLI sessions", + .function = unix_cli_show_cli_sessions, +}; +/* *INDENT-ON* */ + /** CLI command to set terminal pager settings. */ static clib_error_t * unix_cli_set_terminal_pager (vlib_main_t * vm, @@ -3044,11 +3292,14 @@ unix_cli_set_terminal_pager (vlib_main_t * vm, unformat_input_t _line_input, *line_input = &_line_input; clib_error_t *error = 0; + cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + if (!unformat_user (input, unformat_line_input, line_input)) return 0; - cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); - while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "on")) @@ -3100,11 +3351,14 @@ unix_cli_set_terminal_history (vlib_main_t * vm, u32 limit; clib_error_t *error = 0; + cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + if (!unformat_user (input, unformat_line_input, line_input)) return 0; - cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); - while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "on")) @@ -3162,6 +3416,9 @@ unix_cli_set_terminal_ansi (vlib_main_t * vm, cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + if (unformat (input, "on")) cf->ansi_capable = 1; else if (unformat (input, "off")) diff --git a/src/vpp/app/vppctl.c b/src/vpp/app/vppctl.c index 980936f1..f81a0ce9 100644 --- a/src/vpp/app/vppctl.c +++ b/src/vpp/app/vppctl.c @@ -41,12 +41,16 @@ volatile int window_resized = 0; struct termios orig_tio; static void -send_ttype (clib_socket_t * s, int is_dumb) +send_ttype (clib_socket_t * s, int is_interactive) { + char *term; + + term = is_interactive ? getenv ("TERM") : "vppctl"; + if (term == NULL) + term = "dumb"; + clib_socket_tx_add_formatted (s, "%c%c%c" "%c%s" "%c%c", - IAC, SB, TELOPT_TTYPE, - 0, is_dumb ? "dumb" : getenv ("TERM"), - IAC, SE); + IAC, SB, TELOPT_TTYPE, 0, term, IAC, SE); clib_socket_tx (s); } @@ -81,7 +85,8 @@ signal_handler_term (int signum) } static u8 * -process_input (u8 * str, clib_socket_t * s, int is_interactive) +process_input (u8 * str, clib_socket_t * s, int is_interactive, + int *sent_ttype) { int i = 0; @@ -104,7 +109,10 @@ process_input (u8 * str, clib_socket_t * s, int is_interactive) vec_free (sb); i += 2; if (opt == TELOPT_TTYPE) - send_ttype (s, !is_interactive); + { + send_ttype (s, is_interactive); + *sent_ttype = 1; + } else if (is_interactive && opt == TELOPT_NAWS) send_naws (s); } @@ -138,6 +146,8 @@ main (int argc, char *argv[]) u8 *str = 0; u8 *cmd = 0; int do_quit = 0; + int is_interactive = 0; + int sent_ttype = 0; clib_mem_init (0, 64ULL << 10); @@ -164,33 +174,47 @@ main (int argc, char *argv[]) if (error) goto done; - /* Capture terminal resize events */ - memset (&sa, 0, sizeof (struct sigaction)); - sa.sa_handler = signal_handler_winch; + is_interactive = isatty (STDIN_FILENO) && cmd == 0; - if (sigaction (SIGWINCH, &sa, 0) < 0) + if (is_interactive) { - error = clib_error_return_unix (0, "sigaction"); - goto done; - } + /* Capture terminal resize events */ + memset (&sa, 0, sizeof (struct sigaction)); + sa.sa_handler = signal_handler_winch; + if (sigaction (SIGWINCH, &sa, 0) < 0) + { + error = clib_error_return_unix (0, "sigaction"); + goto done; + } - sa.sa_handler = signal_handler_term; - if (sigaction (SIGTERM, &sa, 0) < 0) - { - error = clib_error_return_unix (0, "sigaction"); - goto done; - } + /* Capture SIGTERM to reset tty settings */ + sa.sa_handler = signal_handler_term; + if (sigaction (SIGTERM, &sa, 0) < 0) + { + error = clib_error_return_unix (0, "sigaction"); + goto done; + } - /* Save the original tty state so we can restore it later */ - tcgetattr (STDIN_FILENO, &orig_tio); + /* Save the original tty state so we can restore it later */ + if (tcgetattr (STDIN_FILENO, &orig_tio) < 0) + { + error = clib_error_return_unix (0, "tcgetattr"); + goto done; + } + + /* Tweak the tty settings */ + tio = orig_tio; + /* echo off, canonical mode off, ext'd input processing off */ + tio.c_lflag &= ~(ECHO | ICANON | IEXTEN); + tio.c_cc[VMIN] = 1; /* 1 byte at a time */ + tio.c_cc[VTIME] = 0; /* no timer */ - /* Tweak the tty settings */ - tio = orig_tio; - /* echo off, canonical mode off, ext'd input processing off */ - tio.c_lflag &= ~(ECHO | ICANON | IEXTEN); - tio.c_cc[VMIN] = 1; /* 1 byte at a time */ - tio.c_cc[VTIME] = 0; /* no timer */ - tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio); + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio) < 0) + { + error = clib_error_return_unix (0, "tcsetattr"); + goto done; + } + } efd = epoll_create1 (0); @@ -199,8 +223,12 @@ main (int argc, char *argv[]) event.data.fd = STDIN_FILENO; if (epoll_ctl (efd, EPOLL_CTL_ADD, STDIN_FILENO, &event) != 0) { - error = clib_error_return_unix (0, "epoll_ctl[%d]", STDIN_FILENO); - goto done; + /* ignore EPERM; it means stdin is something like /dev/null */ + if (errno != EPERM) + { + error = clib_error_return_unix (0, "epoll_ctl[%d]", STDIN_FILENO); + goto done; + } } /* register socket */ @@ -240,6 +268,9 @@ main (int argc, char *argv[]) int n; char c[100]; + if (!sent_ttype) + continue; /* not ready for this yet */ + n = read (STDIN_FILENO, c, sizeof (c)); if (n > 0) { @@ -250,6 +281,8 @@ main (int argc, char *argv[]) } else if (n < 0) clib_warning ("read rv=%d", n); + else /* EOF */ + do_quit = 1; } else if (event.data.fd == s->fd) { @@ -260,27 +293,49 @@ main (int argc, char *argv[]) if (clib_socket_rx_end_of_file (s)) break; - str = process_input (str, s, cmd == 0); + str = process_input (str, s, is_interactive, &sent_ttype); if (vec_len (str) > 0) { - n = write (STDOUT_FILENO, str, vec_len (str)); - if (n < 0) + int len = vec_len (str); + u8 *p = str, *q = str; + + while (len) { - error = clib_error_return_unix (0, "write"); - goto done; + /* Search for and skip NUL bytes */ + while (q < (p + len) && *q) + q++; + + n = write (STDOUT_FILENO, p, q - p); + if (n < 0) + { + error = clib_error_return_unix (0, "write"); + goto done; + } + + while (q < (p + len) && !*q) + q++; + len -= q - p; + p = q; } + vec_reset_length (str); } if (do_quit) { - clib_socket_tx_add_formatted (s, "q\n"); + /* Ask the other end to close the connection */ + clib_socket_tx_add_formatted (s, "quit\n"); clib_socket_tx (s); do_quit = 0; } - if (cmd) + if (cmd && sent_ttype) { + /* We wait until after the TELNET TTYPE option has been sent. + * That is to make sure the session at the VPP end has switched + * to line-by-line mode, and thus avoid prompts and echoing. + * Note that it does also disable further TELNET option processing. + */ clib_socket_tx_add_formatted (s, "%s\n", cmd); clib_socket_tx (s); vec_free (cmd); @@ -302,12 +357,15 @@ done: if (efd > -1) close (efd); + if (is_interactive) + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio); + if (error) { clib_error_report (error); return 1; } - tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio); + return 0; } -- cgit 1.2.3-korg From e3c13e8b5b1e799e687fd93783407548e2b96594 Mon Sep 17 00:00:00 2001 From: Chris Luke Date: Thu, 26 Oct 2017 10:44:43 -0400 Subject: Fix for vppctl and interactive commands (VPP-1038) - Interactive commands like "ping" read extra input from the input stream. - In the case of "ping" it is simply a signal to cease the current operation. - "vppctl", in non-interactive mode, will issue a "quit" immediately after the requested command to queue up closing of the session. - This resulted in "ping" thinking a keypress was seen and returning control to the CLI; the "quit" command however is consumed by the keypress event handler and thus the session does not close. - This patch reworks vppctl slightly to only issue "quit" after the command has completed. In particular it uses the fact that VPP issues NUL bytes as a surrogate prompt between output of commands to signal acknowledgement that the command has completed; vppctl now flags that the quit should be issued after the next such acknowledgement. - Since input it still accepted, the user can still terminate the "ping" early, if desired. Change-Id: I7e3dbe767f32f8e364ccb5f81799759b311585df Signed-off-by: Chris Luke --- src/vpp/app/vppctl.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src/vpp/app/vppctl.c') diff --git a/src/vpp/app/vppctl.c b/src/vpp/app/vppctl.c index f81a0ce9..66fe00ab 100644 --- a/src/vpp/app/vppctl.c +++ b/src/vpp/app/vppctl.c @@ -147,6 +147,7 @@ main (int argc, char *argv[]) u8 *cmd = 0; int do_quit = 0; int is_interactive = 0; + int acked = 1; /* counts messages from VPP; starts at 1 */ int sent_ttype = 0; @@ -314,7 +315,10 @@ main (int argc, char *argv[]) } while (q < (p + len) && !*q) - q++; + { + q++; + acked++; /* every NUL is an acknowledgement */ + } len -= q - p; p = q; } @@ -322,7 +326,7 @@ main (int argc, char *argv[]) vec_reset_length (str); } - if (do_quit) + if (do_quit && do_quit < acked) { /* Ask the other end to close the connection */ clib_socket_tx_add_formatted (s, "quit\n"); @@ -339,7 +343,7 @@ main (int argc, char *argv[]) clib_socket_tx_add_formatted (s, "%s\n", cmd); clib_socket_tx (s); vec_free (cmd); - do_quit = 1; + do_quit = acked; /* quit after the next response */ } } else -- cgit 1.2.3-korg