/* * 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_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, 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 *sent_ttype) { 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); *sent_ttype = 1; } 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; int is_interactive = 0; int acked = 1; /* counts messages from VPP; starts at 1 */ int sent_ttype = 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 = CLIB_SOCKET_F_IS_CLIENT; error = clib_socket_init (s); if (error) goto done; is_interactive = isatty (STDIN_FILENO) && cmd == 0; if (is_interactive) { /* 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; } /* 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 */ 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 */ if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio) < 0) { error = clib_error_return_unix (0, "tcsetattr"); goto done; } } 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) { /* 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 */ 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]; if (!sent_ttype) continue; /* not ready for this yet */ 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 /* EOF */ do_quit = 1; } 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, is_interactive, &sent_ttype); if (vec_len (str) > 0) { int len = vec_len (str); u8 *p = str, *q = str; while (len) { /* 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++; acked++; /* every NUL is an acknowledgement */ } len -= q - p; p = q; } vec_reset_length (str); } if (do_quit && do_quit < acked) { /* 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 && 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); do_quit = acked; /* quit after the next response */ } } 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 (is_interactive) tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio); if (error) { clib_error_report (error); return 1; } return 0; } /* *INDENT-ON* */ /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */