From: David Lamparter Date: Wed, 4 Dec 2019 07:10:42 +0000 (+0100) Subject: lib: implement `terminal monitor` for vtysh X-Git-Tag: pim6-testing-20220430~286^2~4 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=0798d2760dbcc95521edf68eff3cf2afaf0a8e4e;p=mirror%2Ffrr.git lib: implement `terminal monitor` for vtysh Adds a new logging target that sends log messages to vtysh. Signed-off-by: David Lamparter --- diff --git a/lib/subdir.am b/lib/subdir.am index 648ab7f14a..64f86b670a 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -113,6 +113,7 @@ lib_libfrr_la_SOURCES = \ lib/zlog.c \ lib/zlog_5424.c \ lib/zlog_5424_cli.c \ + lib/zlog_live.c \ lib/zlog_targets.c \ lib/printf/printf-pos.c \ lib/printf/vfprintf.c \ @@ -287,6 +288,7 @@ pkginclude_HEADERS += \ lib/zebra.h \ lib/zlog.h \ lib/zlog_5424.h \ + lib/zlog_live.h \ lib/zlog_targets.h \ lib/pbr.h \ lib/routing_nb.h \ diff --git a/lib/vty.c b/lib/vty.c index ad9cd719e0..6aa8a0bbb5 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -1314,8 +1314,6 @@ static void vty_read(struct thread *thread) vty_event(VTY_READ, vty); return; } - vty->monitor = 0; /* disable monitoring to avoid - infinite recursion */ flog_err( EC_LIB_SOCKET, "%s: read error on vty client fd %d, closing: %s", @@ -1529,8 +1527,6 @@ static void vty_flush(struct thread *thread) vty->lines >= 0 ? vty->lines : vty->height, erase, 0); switch (flushrc) { case BUFFER_ERROR: - vty->monitor = - 0; /* disable monitoring to avoid infinite recursion */ zlog_info("buffer_flush failed on vty client fd %d/%d, closing", vty->fd, vty->wfd); buffer_reset(vty->lbuf); @@ -2078,8 +2074,6 @@ static int vtysh_flush(struct vty *vty) vty_event(VTYSH_WRITE, vty); break; case BUFFER_ERROR: - vty->monitor = - 0; /* disable monitoring to avoid infinite recursion */ flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing", __func__, vty->fd); buffer_reset(vty->lbuf); @@ -2119,8 +2113,6 @@ static void vtysh_read(struct thread *thread) vty_event(VTYSH_READ, vty); return; } - vty->monitor = 0; /* disable monitoring to avoid - infinite recursion */ flog_err( EC_LIB_SOCKET, "%s: read failed on vtysh client fd %d, closing: %s", @@ -2254,6 +2246,7 @@ void vty_close(struct vty *vty) close(vty->pass_fd); vty->pass_fd = -1; } + zlog_live_close(&vty->live_log); /* Flush buffer. */ buffer_flush_all(vty->obuf, vty->wfd); @@ -2755,8 +2748,9 @@ DEFUN_NOSH (config_who, struct vty *v; frr_each (vtys, vty_sessions, v) - vty_out(vty, "%svty[%d] connected from %s.\n", - v->config ? "*" : " ", v->fd, v->address); + vty_out(vty, "%svty[%d] connected from %s%s.\n", + v->config ? "*" : " ", v->fd, v->address, + zlog_live_is_null(&v->live_log) ? "" : ", live log"); return CMD_SUCCESS; } @@ -2949,35 +2943,56 @@ DEFUN (no_service_advanced_vty, return CMD_SUCCESS; } -DEFUN_NOSH (terminal_monitor, - terminal_monitor_cmd, - "terminal monitor", - "Set terminal line parameters\n" - "Copy debug output to the current terminal line\n") +DEFUN_NOSH(terminal_monitor, + terminal_monitor_cmd, + "terminal monitor [detach]", + "Set terminal line parameters\n" + "Copy debug output to the current terminal line\n" + "Keep logging feed open independent of VTY session\n") { - vty->monitor = 1; + int fd_ret = -1; + + if (vty->type != VTY_SHELL_SERV) { + vty_out(vty, "%% not supported\n"); + return CMD_WARNING; + } + + if (argc == 3) { + struct zlog_live_cfg detach_log = {}; + + zlog_live_open(&detach_log, LOG_DEBUG, &fd_ret); + zlog_live_disown(&detach_log); + } else + zlog_live_open(&vty->live_log, LOG_DEBUG, &fd_ret); + + if (fd_ret == -1) { + vty_out(vty, "%% error opening live log: %m\n"); + return CMD_WARNING; + } + + vty_pass_fd(vty, fd_ret); return CMD_SUCCESS; } -DEFUN_NOSH (terminal_no_monitor, - terminal_no_monitor_cmd, - "terminal no monitor", - "Set terminal line parameters\n" - NO_STR - "Copy debug output to the current terminal line\n") +DEFUN_NOSH(no_terminal_monitor, + no_terminal_monitor_cmd, + "no terminal monitor", + NO_STR + "Set terminal line parameters\n" + "Copy debug output to the current terminal line\n") { - vty->monitor = 0; + zlog_live_close(&vty->live_log); return CMD_SUCCESS; } -DEFUN_NOSH (no_terminal_monitor, - no_terminal_monitor_cmd, - "no terminal monitor", - NO_STR - "Set terminal line parameters\n" - "Copy debug output to the current terminal line\n") +DEFUN_NOSH(terminal_no_monitor, + terminal_no_monitor_cmd, + "terminal no monitor", + "Set terminal line parameters\n" + NO_STR + "Copy debug output to the current terminal line\n") { - return terminal_no_monitor(self, vty, argc, argv); + return no_terminal_monitor(self, vty, argc, argv); } diff --git a/lib/vty.h b/lib/vty.h index 92fbb468a9..e42a3b210f 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -34,6 +34,7 @@ #include "qobj.h" #include "compiler.h" #include "northbound.h" +#include "zlog_live.h" #ifdef __cplusplus extern "C" { @@ -175,6 +176,9 @@ struct vty { /* CLI command return value (likely CMD_SUCCESS) when pass_fd != -1 */ uint8_t pass_fd_status[4]; + /* live logging target / terminal monitor */ + struct zlog_live_cfg live_log; + /* IAC handling: was the last character received the IAC (interpret-as-command) escape character (and therefore the next character will be the command code)? Refer to Telnet RFC 854. */ @@ -198,9 +202,6 @@ struct vty { /* Configure lines. */ int lines; - /* Terminal monitor. */ - int monitor; - /* Read and write thread. */ struct thread *t_read; struct thread *t_write; diff --git a/lib/zlog_live.c b/lib/zlog_live.c new file mode 100644 index 0000000000..fbe0e5ee49 --- /dev/null +++ b/lib/zlog_live.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2019-22 David Lamparter, for NetDEF, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "zebra.h" + +#include "zlog_live.h" + +#include "memory.h" +#include "frrcu.h" +#include "zlog.h" +#include "printfrr.h" + +DEFINE_MTYPE_STATIC(LOG, LOG_LIVE, "log vtysh live target"); + +enum { + STATE_NORMAL = 0, + STATE_FD_DEAD, + STATE_DISOWNED, +}; + +struct zlt_live { + struct zlog_target zt; + + atomic_uint_fast32_t fd; + struct rcu_head_close head_close; + struct rcu_head head_self; + + atomic_uint_fast32_t state; +}; + +static void zlog_live(struct zlog_target *zt, struct zlog_msg *msgs[], + size_t nmsgs) +{ + struct zlt_live *zte = container_of(zt, struct zlt_live, zt); + struct zlog_live_hdr hdrs[nmsgs], *hdr = hdrs; + struct mmsghdr mmhs[nmsgs], *mmh = mmhs; + struct iovec iovs[nmsgs * 3], *iov = iovs; + struct timespec ts; + size_t i, textlen; + int fd; + uint_fast32_t state; + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + + if (fd < 0) + return; + + memset(mmhs, 0, sizeof(mmhs)); + memset(hdrs, 0, sizeof(hdrs)); + + for (i = 0; i < nmsgs; i++) { + const struct fmt_outpos *argpos; + size_t n_argpos, arghdrlen; + struct zlog_msg *msg = msgs[i]; + int prio = zlog_msg_prio(msg); + + if (prio > zt->prio_min) + continue; + + zlog_msg_args(msg, &arghdrlen, &n_argpos, &argpos); + + mmh->msg_hdr.msg_iov = iov; + + iov->iov_base = hdr; + iov->iov_len = sizeof(*hdr); + iov++; + + if (n_argpos) { + iov->iov_base = (char *)argpos; + iov->iov_len = sizeof(*argpos) * n_argpos; + iov++; + } + + iov->iov_base = (char *)zlog_msg_text(msg, &textlen); + iov->iov_len = textlen; + iov++; + + zlog_msg_tsraw(msg, &ts); + + hdr->ts_sec = ts.tv_sec; + hdr->ts_nsec = ts.tv_nsec; + hdr->prio = zlog_msg_prio(msg); + hdr->flags = 0; + hdr->textlen = textlen; + hdr->arghdrlen = arghdrlen; + hdr->n_argpos = n_argpos; + + mmh->msg_hdr.msg_iovlen = iov - mmh->msg_hdr.msg_iov; + mmh++; + hdr++; + } + + size_t msgtotal = mmh - mmhs; + ssize_t sent; + + for (size_t msgpos = 0; msgpos < msgtotal; msgpos += sent) { + sent = sendmmsg(fd, mmhs + msgpos, msgtotal - msgpos, 0); + + if (sent <= 0) + goto out_err; + } + return; + +out_err: + fd = atomic_exchange_explicit(&zte->fd, -1, memory_order_relaxed); + if (fd < 0) + return; + + rcu_close(&zte->head_close, fd); + zlog_target_replace(zt, NULL); + + state = STATE_NORMAL; + atomic_compare_exchange_strong_explicit( + &zte->state, &state, STATE_FD_DEAD, memory_order_relaxed, + memory_order_relaxed); + if (state == STATE_DISOWNED) + rcu_free(MTYPE_LOG_LIVE, zte, head_self); +} + +static void zlog_live_sigsafe(struct zlog_target *zt, const char *text, + size_t len) +{ + struct zlt_live *zte = container_of(zt, struct zlt_live, zt); + struct zlog_live_hdr hdr[1]; + struct iovec iovs[2], *iov = iovs; + struct timespec ts; + int fd; + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + if (fd < 0) + return; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + hdr->ts_sec = ts.tv_sec; + hdr->ts_nsec = ts.tv_nsec; + hdr->prio = LOG_CRIT; + hdr->flags = 0; + hdr->textlen = len; + hdr->n_argpos = 0; + + iov->iov_base = (char *)hdr; + iov->iov_len = sizeof(hdr); + iov++; + + iov->iov_base = (char *)text; + iov->iov_len = len; + iov++; + + writev(fd, iovs, iov - iovs); +} + +void zlog_live_open(struct zlog_live_cfg *cfg, int prio_min, int *other_fd) +{ + int sockets[2]; + struct zlt_live *zte; + struct zlog_target *zt; + + if (cfg->target) + zlog_live_close(cfg); + + *other_fd = -1; + if (prio_min == ZLOG_DISABLED) + return; + + /* the only reason for SEQPACKET here is getting close notifications. + * otherwise if you open a bunch of vtysh connections with live logs + * and close them all, the fds will stick around until we get an error + * when trying to log something to them at some later point -- which + * eats up fds and might be *much* later for some daemons. + */ + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) < 0) { + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) { + zlog_warn("%% could not open socket pair: %m"); + return; + } + } else + /* SEQPACKET only: try to zap read direction */ + shutdown(sockets[0], SHUT_RD); + + *other_fd = sockets[1]; + + zt = zlog_target_clone(MTYPE_LOG_LIVE, NULL, sizeof(*zte)); + zte = container_of(zt, struct zlt_live, zt); + cfg->target = zte; + + zte->fd = sockets[0]; + zte->zt.prio_min = prio_min; + zte->zt.logfn = zlog_live; + zte->zt.logfn_sigsafe = zlog_live_sigsafe; + + zlog_target_replace(NULL, zt); +} + +void zlog_live_close(struct zlog_live_cfg *cfg) +{ + struct zlt_live *zte; + int fd; + + if (!cfg->target) + return; + + zte = cfg->target; + cfg->target = NULL; + + fd = atomic_exchange_explicit(&zte->fd, -1, memory_order_relaxed); + + if (fd >= 0) { + rcu_close(&zte->head_close, fd); + zlog_target_replace(&zte->zt, NULL); + } + rcu_free(MTYPE_LOG_LIVE, zte, head_self); +} + +void zlog_live_disown(struct zlog_live_cfg *cfg) +{ + struct zlt_live *zte; + uint_fast32_t state; + + if (!cfg->target) + return; + + zte = cfg->target; + cfg->target = NULL; + + state = STATE_NORMAL; + atomic_compare_exchange_strong_explicit( + &zte->state, &state, STATE_DISOWNED, memory_order_relaxed, + memory_order_relaxed); + if (state == STATE_FD_DEAD) + rcu_free(MTYPE_LOG_LIVE, zte, head_self); +} diff --git a/lib/zlog_live.h b/lib/zlog_live.h new file mode 100644 index 0000000000..c948baeab1 --- /dev/null +++ b/lib/zlog_live.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019-22 David Lamparter, for NetDEF, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _FRR_ZLOG_LIVE_H +#define _FRR_ZLOG_LIVE_H + +#include "printfrr.h" + +struct zlog_live_hdr { + uint64_t ts_sec; + uint32_t ts_nsec; + uint32_t prio; + uint32_t flags; + uint32_t textlen; + + uint32_t arghdrlen; + uint32_t n_argpos; + struct fmt_outpos argpos[0]; +}; + +struct zlt_live; + +struct zlog_live_cfg { + struct zlt_live *target; + + /* nothing else here */ +}; + +extern void zlog_live_open(struct zlog_live_cfg *cfg, int prio_min, + int *other_fd); + +static inline bool zlog_live_is_null(struct zlog_live_cfg *cfg) +{ + return cfg->target == NULL; +} + +extern void zlog_live_close(struct zlog_live_cfg *cfg); +extern void zlog_live_disown(struct zlog_live_cfg *cfg); + +#endif /* _FRR_ZLOG_5424_H */