lib: implement terminal monitor for vtysh

Adds a new logging target that sends log messages to vtysh.

Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
This commit is contained in:
David Lamparter 2019-12-04 08:10:42 +01:00 committed by David Lamparter
parent b2dde56b2c
commit 0798d2760d
5 changed files with 349 additions and 33 deletions

View File

@ -113,6 +113,7 @@ lib_libfrr_la_SOURCES = \
lib/zlog.c \ lib/zlog.c \
lib/zlog_5424.c \ lib/zlog_5424.c \
lib/zlog_5424_cli.c \ lib/zlog_5424_cli.c \
lib/zlog_live.c \
lib/zlog_targets.c \ lib/zlog_targets.c \
lib/printf/printf-pos.c \ lib/printf/printf-pos.c \
lib/printf/vfprintf.c \ lib/printf/vfprintf.c \
@ -287,6 +288,7 @@ pkginclude_HEADERS += \
lib/zebra.h \ lib/zebra.h \
lib/zlog.h \ lib/zlog.h \
lib/zlog_5424.h \ lib/zlog_5424.h \
lib/zlog_live.h \
lib/zlog_targets.h \ lib/zlog_targets.h \
lib/pbr.h \ lib/pbr.h \
lib/routing_nb.h \ lib/routing_nb.h \

View File

@ -1314,8 +1314,6 @@ static void vty_read(struct thread *thread)
vty_event(VTY_READ, vty); vty_event(VTY_READ, vty);
return; return;
} }
vty->monitor = 0; /* disable monitoring to avoid
infinite recursion */
flog_err( flog_err(
EC_LIB_SOCKET, EC_LIB_SOCKET,
"%s: read error on vty client fd %d, closing: %s", "%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); vty->lines >= 0 ? vty->lines : vty->height, erase, 0);
switch (flushrc) { switch (flushrc) {
case BUFFER_ERROR: case BUFFER_ERROR:
vty->monitor =
0; /* disable monitoring to avoid infinite recursion */
zlog_info("buffer_flush failed on vty client fd %d/%d, closing", zlog_info("buffer_flush failed on vty client fd %d/%d, closing",
vty->fd, vty->wfd); vty->fd, vty->wfd);
buffer_reset(vty->lbuf); buffer_reset(vty->lbuf);
@ -2078,8 +2074,6 @@ static int vtysh_flush(struct vty *vty)
vty_event(VTYSH_WRITE, vty); vty_event(VTYSH_WRITE, vty);
break; break;
case BUFFER_ERROR: case BUFFER_ERROR:
vty->monitor =
0; /* disable monitoring to avoid infinite recursion */
flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing", flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing",
__func__, vty->fd); __func__, vty->fd);
buffer_reset(vty->lbuf); buffer_reset(vty->lbuf);
@ -2119,8 +2113,6 @@ static void vtysh_read(struct thread *thread)
vty_event(VTYSH_READ, vty); vty_event(VTYSH_READ, vty);
return; return;
} }
vty->monitor = 0; /* disable monitoring to avoid
infinite recursion */
flog_err( flog_err(
EC_LIB_SOCKET, EC_LIB_SOCKET,
"%s: read failed on vtysh client fd %d, closing: %s", "%s: read failed on vtysh client fd %d, closing: %s",
@ -2254,6 +2246,7 @@ void vty_close(struct vty *vty)
close(vty->pass_fd); close(vty->pass_fd);
vty->pass_fd = -1; vty->pass_fd = -1;
} }
zlog_live_close(&vty->live_log);
/* Flush buffer. */ /* Flush buffer. */
buffer_flush_all(vty->obuf, vty->wfd); buffer_flush_all(vty->obuf, vty->wfd);
@ -2755,8 +2748,9 @@ DEFUN_NOSH (config_who,
struct vty *v; struct vty *v;
frr_each (vtys, vty_sessions, v) frr_each (vtys, vty_sessions, v)
vty_out(vty, "%svty[%d] connected from %s.\n", vty_out(vty, "%svty[%d] connected from %s%s.\n",
v->config ? "*" : " ", v->fd, v->address); v->config ? "*" : " ", v->fd, v->address,
zlog_live_is_null(&v->live_log) ? "" : ", live log");
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@ -2949,35 +2943,56 @@ DEFUN (no_service_advanced_vty,
return CMD_SUCCESS; return CMD_SUCCESS;
} }
DEFUN_NOSH (terminal_monitor, DEFUN_NOSH(terminal_monitor,
terminal_monitor_cmd, terminal_monitor_cmd,
"terminal monitor", "terminal monitor [detach]",
"Set terminal line parameters\n" "Set terminal line parameters\n"
"Copy debug output to the current terminal line\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; return CMD_SUCCESS;
} }
DEFUN_NOSH (terminal_no_monitor, DEFUN_NOSH(no_terminal_monitor,
terminal_no_monitor_cmd, no_terminal_monitor_cmd,
"terminal no monitor", "no terminal monitor",
"Set terminal line parameters\n" NO_STR
NO_STR "Set terminal line parameters\n"
"Copy debug output to the current terminal line\n") "Copy debug output to the current terminal line\n")
{ {
vty->monitor = 0; zlog_live_close(&vty->live_log);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
DEFUN_NOSH (no_terminal_monitor, DEFUN_NOSH(terminal_no_monitor,
no_terminal_monitor_cmd, terminal_no_monitor_cmd,
"no terminal monitor", "terminal no monitor",
NO_STR "Set terminal line parameters\n"
"Set terminal line parameters\n" NO_STR
"Copy debug output to the current terminal line\n") "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);
} }

View File

@ -34,6 +34,7 @@
#include "qobj.h" #include "qobj.h"
#include "compiler.h" #include "compiler.h"
#include "northbound.h" #include "northbound.h"
#include "zlog_live.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -175,6 +176,9 @@ struct vty {
/* CLI command return value (likely CMD_SUCCESS) when pass_fd != -1 */ /* CLI command return value (likely CMD_SUCCESS) when pass_fd != -1 */
uint8_t pass_fd_status[4]; 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 handling: was the last character received the
IAC (interpret-as-command) escape character (and therefore the next IAC (interpret-as-command) escape character (and therefore the next
character will be the command code)? Refer to Telnet RFC 854. */ character will be the command code)? Refer to Telnet RFC 854. */
@ -198,9 +202,6 @@ struct vty {
/* Configure lines. */ /* Configure lines. */
int lines; int lines;
/* Terminal monitor. */
int monitor;
/* Read and write thread. */ /* Read and write thread. */
struct thread *t_read; struct thread *t_read;
struct thread *t_write; struct thread *t_write;

245
lib/zlog_live.c Normal file
View File

@ -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);
}

53
lib/zlog_live.h Normal file
View File

@ -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 */