summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/command.h1
-rw-r--r--lib/log_vty.c3
-rw-r--r--lib/subdir.am4
-rw-r--r--lib/zlog_5424.c1146
-rw-r--r--lib/zlog_5424.h152
-rw-r--r--lib/zlog_5424_cli.c1006
6 files changed, 2312 insertions, 0 deletions
diff --git a/lib/command.h b/lib/command.h
index c888356d61..902d39ebe2 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -93,6 +93,7 @@ enum node_type {
RMAP_DEBUG_NODE, /* Route-map debug node */
RESOLVER_DEBUG_NODE, /* Resolver debug node */
AAA_NODE, /* AAA node. */
+ EXTLOG_NODE, /* RFC5424 & co. extended syslog */
KEYCHAIN_NODE, /* Key-chain node. */
KEYCHAIN_KEY_NODE, /* Key-chain key node. */
IP_NODE, /* Static ip route node. */
diff --git a/lib/log_vty.c b/lib/log_vty.c
index 621949ab57..d50e2788ef 100644
--- a/lib/log_vty.c
+++ b/lib/log_vty.c
@@ -24,6 +24,7 @@
#include "command.h"
#include "lib/log.h"
#include "lib/zlog_targets.h"
+#include "lib/zlog_5424.h"
#include "lib/lib_errors.h"
#include "lib/printfrr.h"
@@ -861,4 +862,6 @@ void log_cmd_init(void)
install_element(ENABLE_NODE, &debug_uid_backtrace_cmd);
install_element(CONFIG_NODE, &debug_uid_backtrace_cmd);
+
+ log_5424_cmd_init();
}
diff --git a/lib/subdir.am b/lib/subdir.am
index bb10d71ed1..a56bbb0c8d 100644
--- a/lib/subdir.am
+++ b/lib/subdir.am
@@ -110,6 +110,8 @@ lib_libfrr_la_SOURCES = \
lib/yang_wrappers.c \
lib/zclient.c \
lib/zlog.c \
+ lib/zlog_5424.c \
+ lib/zlog_5424_cli.c \
lib/zlog_targets.c \
lib/printf/printf-pos.c \
lib/printf/vfprintf.c \
@@ -168,6 +170,7 @@ clippy_scan += \
lib/routemap_cli.c \
lib/thread.c \
lib/vty.c \
+ lib/zlog_5424_cli.c \
# end
pkginclude_HEADERS += \
@@ -281,6 +284,7 @@ pkginclude_HEADERS += \
lib/zclient.h \
lib/zebra.h \
lib/zlog.h \
+ lib/zlog_5424.h \
lib/zlog_targets.h \
lib/pbr.h \
lib/routing_nb.h \
diff --git a/lib/zlog_5424.c b/lib/zlog_5424.c
new file mode 100644
index 0000000000..740d6bfba8
--- /dev/null
+++ b/lib/zlog_5424.c
@@ -0,0 +1,1146 @@
+/*
+ * Copyright (c) 2015-21 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.
+ */
+
+/* when you work on this code, please install a fuzzer (e.g. AFL) and run
+ * tests/lib/fuzz_zlog.c
+ *
+ * The most likely type of bug in this code is an off-by-one error in the
+ * buffer management pieces, and this isn't easily covered by an unit test
+ * or topotests. Fuzzing is the best tool here, but the CI can't do that
+ * since it's quite resource intensive.
+ */
+
+#include "zebra.h"
+
+#include "zlog_5424.h"
+
+#include <sys/un.h>
+#include <syslog.h>
+
+#include "memory.h"
+#include "frrcu.h"
+#include "printfrr.h"
+#include "typerb.h"
+#include "frr_pthread.h"
+#include "command.h"
+#include "monotime.h"
+#include "thread.h"
+
+#include "lib/version.h"
+#include "lib/lib_errors.h"
+
+DEFINE_MTYPE_STATIC(LOG, LOG_5424, "extended log target");
+DEFINE_MTYPE_STATIC(LOG, LOG_5424_ROTATE, "extended log rotate helper");
+
+/* the actual log target data structure
+ *
+ * remember this is RCU'd by the core zlog functions. Changing anything
+ * works by allocating a new struct, filling it, adding it, and removing the
+ * old one.
+ */
+struct zlt_5424 {
+ struct zlog_target zt;
+
+ atomic_uint_fast32_t fd;
+
+ enum zlog_5424_format fmt;
+ uint32_t ts_flags;
+ int facility;
+
+ /* the various extra pieces to add... */
+ bool kw_version : 1;
+ bool kw_location : 1;
+ bool kw_uid : 1;
+ bool kw_ec : 1;
+ bool kw_args : 1;
+
+ /* some formats may or may not include the trailing \n */
+ bool use_nl : 1;
+
+ /* for DGRAM & SEQPACKET sockets, send 1 log message per packet, since
+ * the socket preserves packet boundaries. On Linux, this uses
+ * sendmmsg() for efficiency, on other systems we need a syscall each.
+ */
+ bool packets : 1;
+
+ /* for DGRAM, in order to not have to reconnect, we need to use
+ * sendto()/sendmsg() with the destination given; otherwise we'll get
+ * ENOTCONN. (We do a connect(), which serves to verify the type of
+ * socket, but if the receiver goes away, the kernel disconnects the
+ * socket so writev() no longer works since the destination is now
+ * unspecified.)
+ */
+ struct sockaddr_storage sa;
+ socklen_t sa_len;
+
+ /* these are both getting set, but current_err is cleared on success,
+ * so we know whether the error is current or past.
+ */
+ int last_err, current_err;
+ atomic_size_t lost_msgs;
+ struct timeval last_err_ts;
+
+ struct rcu_head_close head_close;
+};
+
+static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type);
+
+/* rough header length estimate
+ * ============================
+ *
+ * ^ = might grow
+ *
+ * 49^ longest filename (pceplib/test/pcep_utils_double_linked_list_test.h)
+ * 5^ highest line number (48530, bgpd/bgp_nb_config.c)
+ * 65^ longest function name
+ * (lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_destroy)
+ * 11 unique id ("XXXXX-XXXXX")
+ * 10 EC ("4294967295" or "0xffffffff")
+ * 35 ISO8601 TS at full length ("YYYY-MM-DD HH:MM:SS.NNNNNNNNN+ZZ:ZZ")
+ * ---
+ * 175
+ *
+ * rarely used (hopefully...):
+ * 26^ FRR_VERSION ("10.10.10-dev-gffffffffffff")
+ * ---
+ * 201
+ *
+ * x16 highest number of format parameters currently
+ * 40 estimate for hostname + 2*daemon + pid
+ *
+ * specific format overhead:
+ *
+ * RFC3164 - shorter than the others
+ * RFC5424 - 175 + "<999>1 "=7 + 52 (location@50145) + 40 (host/...)
+ * rarely: + 65 + 26 (for [origin])
+ * args: 16 * (8 + per-arg (20?)) = ~448
+ *
+ * so without "args@", origin or (future) keywords, around 256 seems OK
+ * with args@ and/or origin and/or keywords, 512 seems more reasonable
+ *
+ * but - note the code allocates this amount multiplied by the number of
+ * messages in the incoming batch (minimum 3), this means short messages and
+ * long messages smooth each other out.
+ *
+ * Since the code handles space-exceeded by grabbing a bunch of stack memory,
+ * a reasonable middle ground estimate is desirable here, so ...
+ * *drumroll*
+ * let's go with 128 + args?128. (remember the minimum 3 multiplier)
+ *
+ * low_space is the point where we don't try to fit another message in & just
+ * submit what we have to the kernel.
+ *
+ * The zlog code only buffers debug & informational messages, so in production
+ * usage most of the calls will be writing out only 1 message. This makes
+ * the min *3 multiplier quite useful.
+ */
+
+static inline size_t zlog_5424_bufsz(struct zlt_5424 *zte, size_t nmsgs,
+ size_t *low_space)
+{
+ size_t ret = 128;
+
+ if (zte->kw_args)
+ ret += 128;
+ *low_space = ret;
+ return ret * MAX(nmsgs, 3);
+}
+
+struct state {
+ struct fbuf *fbuf;
+ struct iovec *iov;
+};
+
+/* stack-based keyword support is likely to bump this to 3 or 4 */
+#define IOV_PER_MSG 2
+_Static_assert(IOV_MAX >= IOV_PER_MSG,
+ "this code won't work with IOV_MAX < IOV_PER_MSG");
+
+/* the following functions are quite similar, but trying to merge them just
+ * makes a big mess. check the others when touching one.
+ *
+ * timestamp keywords hostname
+ * RFC5424 ISO8601 yes yes
+ * RFC3164 RFC3164 no yes
+ * local RFC3164 no no
+ * journald ISO8601(unused) yes (unused)
+ */
+
+static size_t zlog_5424_one(struct zlt_5424 *zte, struct zlog_msg *msg,
+ struct state *state)
+{
+ size_t textlen;
+ struct fbuf *fbuf = state->fbuf;
+ char *orig_pos = fbuf->pos;
+ size_t need = 0;
+ int prio = zlog_msg_prio(msg);
+ intmax_t pid, tid;
+
+ zlog_msg_pid(msg, &pid, &tid);
+
+ need += bprintfrr(fbuf, "<%d>1 ", prio | zte->facility);
+ need += zlog_msg_ts(msg, fbuf, zte->ts_flags);
+ need += bprintfrr(fbuf, " %s %s %jd %.*s ", cmd_hostname_get() ?: "-",
+ zlog_progname, pid, (int)(zlog_prefixsz - 2),
+ zlog_prefix);
+
+ if (zte->kw_version)
+ need += bprintfrr(
+ fbuf,
+ "[origin enterpriseId=\"50145\" software=\"FRRouting\" swVersion=\"%s\"]",
+ FRR_VERSION);
+
+ const struct xref_logmsg *xref;
+ struct xrefdata *xrefdata;
+
+ need += bprintfrr(fbuf, "[location@50145 tid=\"%jd\"", tid);
+ if (zlog_instance > 0)
+ need += bprintfrr(fbuf, " instance=\"%d\"", zlog_instance);
+
+ xref = zlog_msg_xref(msg);
+ xrefdata = xref ? xref->xref.xrefdata : NULL;
+ if (xrefdata) {
+ if (zte->kw_uid)
+ need += bprintfrr(fbuf, " id=\"%s\"", xrefdata->uid);
+ if (zte->kw_ec && prio <= LOG_WARNING)
+ need += bprintfrr(fbuf, " ec=\"%u\"", xref->ec);
+ if (zte->kw_location)
+ need += bprintfrr(
+ fbuf, " file=\"%s\" line=\"%d\" func=\"%s\"",
+ xref->xref.file, xref->xref.line,
+ xref->xref.func);
+ }
+ need += bputch(fbuf, ']');
+
+ size_t hdrlen, n_argpos;
+ const struct fmt_outpos *argpos;
+ const char *text;
+
+ text = zlog_msg_text(msg, &textlen);
+ zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos);
+
+ if (zte->kw_args && n_argpos) {
+ need += bputs(fbuf, "[args@50145");
+
+ for (size_t i = 0; i < n_argpos; i++) {
+ int len = argpos[i].off_end - argpos[i].off_start;
+
+ need += bprintfrr(fbuf, " arg%zu=%*pSQsq", i + 1, len,
+ text + argpos[i].off_start);
+ }
+
+ need += bputch(fbuf, ']');
+ }
+
+ need += bputch(fbuf, ' ');
+
+ if (orig_pos + need > fbuf->buf + fbuf->len) {
+ /* not enough space in the buffer for headers. the loop in
+ * zlog_5424() will flush other messages that are already in
+ * the buffer, grab a bigger buffer if needed, and try again.
+ */
+ fbuf->pos = orig_pos;
+ return need;
+ }
+
+ /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */
+ state->iov->iov_base = orig_pos;
+ state->iov->iov_len = fbuf->pos - orig_pos;
+ state->iov++;
+
+ state->iov->iov_base = (char *)text + hdrlen;
+ state->iov->iov_len = textlen - hdrlen + zte->use_nl;
+ state->iov++;
+ return 0;
+}
+
+static size_t zlog_3164_one(struct zlt_5424 *zte, struct zlog_msg *msg,
+ struct state *state)
+{
+ size_t textlen;
+ struct fbuf *fbuf = state->fbuf;
+ char *orig_pos = fbuf->pos;
+ size_t need = 0;
+ int prio = zlog_msg_prio(msg);
+ intmax_t pid, tid;
+
+ zlog_msg_pid(msg, &pid, &tid);
+
+ need += bprintfrr(fbuf, "<%d>", prio | zte->facility);
+ need += zlog_msg_ts_3164(msg, fbuf, zte->ts_flags);
+ if (zte->fmt != ZLOG_FMT_LOCAL) {
+ need += bputch(fbuf, ' ');
+ need += bputs(fbuf, cmd_hostname_get() ?: "-");
+ }
+ need += bprintfrr(fbuf, " %s[%jd]: ", zlog_progname, pid);
+
+ if (orig_pos + need > fbuf->buf + fbuf->len) {
+ /* not enough space in the buffer for headers. loop in
+ * zlog_5424() will flush other messages that are already in
+ * the buffer, grab a bigger buffer if needed, and try again.
+ */
+ fbuf->pos = orig_pos;
+ return need;
+ }
+
+ /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */
+ state->iov->iov_base = orig_pos;
+ state->iov->iov_len = fbuf->pos - orig_pos;
+ state->iov++;
+
+ state->iov->iov_base = (char *)zlog_msg_text(msg, &textlen);
+ state->iov->iov_len = textlen + zte->use_nl;
+ state->iov++;
+ return 0;
+}
+
+static size_t zlog_journald_one(struct zlt_5424 *zte, struct zlog_msg *msg,
+ struct state *state)
+{
+ size_t textlen;
+ struct fbuf *fbuf = state->fbuf;
+ char *orig_pos = fbuf->pos;
+ size_t need = 0;
+ int prio = zlog_msg_prio(msg);
+ intmax_t pid, tid;
+
+ zlog_msg_pid(msg, &pid, &tid);
+
+ need += bprintfrr(fbuf,
+ "PRIORITY=%d\n"
+ "SYSLOG_FACILITY=%d\n"
+ "TID=%jd\n"
+ "FRR_DAEMON=%s\n"
+ "SYSLOG_TIMESTAMP=",
+ prio, zte->facility, tid, zlog_progname);
+ need += zlog_msg_ts(msg, fbuf, zte->ts_flags);
+ need += bputch(fbuf, '\n');
+ if (zlog_instance > 0)
+ need += bprintfrr(fbuf, "FRR_INSTANCE=%d\n", zlog_instance);
+
+ const struct xref_logmsg *xref;
+ struct xrefdata *xrefdata;
+
+ xref = zlog_msg_xref(msg);
+ xrefdata = xref ? xref->xref.xrefdata : NULL;
+ if (xrefdata) {
+ if (zte->kw_uid && xrefdata->uid[0])
+ need += bprintfrr(fbuf, "FRR_ID=%s\n", xrefdata->uid);
+ if (zte->kw_ec && prio <= LOG_WARNING)
+ need += bprintfrr(fbuf, "FRR_EC=%d\n", xref->ec);
+ if (zte->kw_location)
+ need += bprintfrr(fbuf,
+ "CODE_FILE=%s\n"
+ "CODE_LINE=%d\n"
+ "CODE_FUNC=%s\n",
+ xref->xref.file, xref->xref.line,
+ xref->xref.func);
+ }
+
+ size_t hdrlen, n_argpos;
+ const struct fmt_outpos *argpos;
+ const char *text;
+
+ text = zlog_msg_text(msg, &textlen);
+ zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos);
+
+ if (zte->kw_args && n_argpos) {
+ for (size_t i = 0; i < n_argpos; i++) {
+ int len = argpos[i].off_end - argpos[i].off_start;
+
+ /* rather than escape the value, we could use
+ * journald's binary encoding, but that seems a bit
+ * excessive/unnecessary. 99% of things we print here
+ * will just output 1:1 with %pSE.
+ */
+ need += bprintfrr(fbuf, "FRR_ARG%zu=%*pSE\n", i + 1,
+ len, text + argpos[i].off_start);
+ }
+ }
+
+ need += bputs(fbuf, "MESSAGE=");
+
+ if (orig_pos + need > fbuf->buf + fbuf->len) {
+ /* not enough space in the buffer for headers. loop in
+ * zlog_5424() will flush other messages that are already in
+ * the buffer, grab a bigger buffer if needed, and try again.
+ */
+ fbuf->pos = orig_pos;
+ return need;
+ }
+
+ /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */
+ state->iov->iov_base = orig_pos;
+ state->iov->iov_len = fbuf->pos - orig_pos;
+ state->iov++;
+
+ state->iov->iov_base = (char *)text + hdrlen;
+ state->iov->iov_len = textlen - hdrlen + 1;
+ state->iov++;
+ return 0;
+}
+
+static size_t zlog_one(struct zlt_5424 *zte, struct zlog_msg *msg,
+ struct state *state)
+{
+ switch (zte->fmt) {
+ case ZLOG_FMT_5424:
+ return zlog_5424_one(zte, msg, state);
+ case ZLOG_FMT_3164:
+ case ZLOG_FMT_LOCAL:
+ return zlog_3164_one(zte, msg, state);
+ case ZLOG_FMT_JOURNALD:
+ return zlog_journald_one(zte, msg, state);
+ }
+ return 0;
+}
+
+static void zlog_5424_err(struct zlt_5424 *zte, size_t count)
+{
+ if (!count) {
+ zte->current_err = 0;
+ return;
+ }
+
+ /* only the counter is atomic because otherwise it'd be meaningless */
+ atomic_fetch_add_explicit(&zte->lost_msgs, count, memory_order_relaxed);
+
+ /* these are non-atomic and can provide wrong results when read, but
+ * since they're only for debugging / display, that's OK.
+ */
+ zte->current_err = zte->last_err = errno;
+ monotime(&zte->last_err_ts);
+}
+
+static void zlog_5424(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs)
+{
+ size_t i;
+ struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt);
+ int fd, ret;
+ size_t niov = MIN(IOV_PER_MSG * nmsgs, IOV_MAX);
+ struct iovec iov[niov], *iov_last = iov + niov;
+ struct mmsghdr mmsg[zte->packets ? nmsgs : 1], *mpos = mmsg;
+ size_t count = 0;
+
+ /* refer to size estimate at top of file */
+ size_t low_space;
+ char hdr_buf[zlog_5424_bufsz(zte, nmsgs, &low_space)];
+ struct fbuf hdr_pos = {
+ .buf = hdr_buf,
+ .pos = hdr_buf,
+ .len = sizeof(hdr_buf),
+ };
+ struct state state = {
+ .fbuf = &hdr_pos,
+ .iov = iov,
+ };
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+ memset(mmsg, 0, sizeof(mmsg));
+ if (zte->sa_len) {
+ for (i = 0; i < array_size(mmsg); i++) {
+ mmsg[i].msg_hdr.msg_name = (struct sockaddr *)&zte->sa;
+ mmsg[i].msg_hdr.msg_namelen = zte->sa_len;
+ }
+ }
+ mmsg[0].msg_hdr.msg_iov = iov;
+
+ for (i = 0; i < nmsgs; i++) {
+ int prio = zlog_msg_prio(msgs[i]);
+ size_t need = 0;
+
+ if (prio <= zte->zt.prio_min) {
+ if (zte->packets)
+ mpos->msg_hdr.msg_iov = state.iov;
+
+ need = zlog_one(zte, msgs[i], &state);
+
+ if (zte->packets) {
+ mpos->msg_hdr.msg_iovlen =
+ state.iov - mpos->msg_hdr.msg_iov;
+ mpos++;
+ }
+ count++;
+ }
+
+ /* clang-format off */
+ if ((need
+ || (size_t)(hdr_pos.buf + hdr_pos.len - hdr_pos.pos)
+ < low_space
+ || i + 1 == nmsgs
+ || state.iov + IOV_PER_MSG > iov_last)
+ && state.iov > iov) {
+ /* clang-format on */
+
+ if (zte->packets) {
+ struct mmsghdr *sendpos;
+
+ for (sendpos = mmsg; sendpos < mpos;) {
+ ret = sendmmsg(fd, sendpos,
+ mpos - sendpos, 0);
+ if (ret <= 0)
+ break;
+ sendpos += ret;
+ }
+ zlog_5424_err(zte, mpos - sendpos);
+ mpos = mmsg;
+ } else {
+ if (!zte->sa_len)
+ ret = writev(fd, iov, state.iov - iov);
+ else {
+ mpos->msg_hdr.msg_iovlen =
+ state.iov - iov;
+ ret = sendmsg(fd, &mpos->msg_hdr, 0);
+ }
+
+ if (ret < 0)
+ zlog_5424_err(zte, count);
+ else
+ zlog_5424_err(zte, 0);
+ }
+
+ count = 0;
+ hdr_pos.pos = hdr_buf;
+ state.iov = iov;
+ }
+
+ /* if need == 0, we just put a message (or nothing) in the
+ * buffer and are continuing for more to batch in a single
+ * writev()
+ */
+ if (need == 0)
+ continue;
+
+ if (need && need <= sizeof(hdr_buf)) {
+ /* don't need to allocate, just try this msg
+ * again without other msgs already using up
+ * buffer space
+ */
+ i--;
+ continue;
+ }
+
+ /* need > sizeof(hdr_buf), need to grab some memory. Taking
+ * it off the stack is fine here.
+ */
+ char buf2[need];
+ struct fbuf fbuf2 = {
+ .buf = buf2,
+ .pos = buf2,
+ .len = sizeof(buf2),
+ };
+
+ state.fbuf = &fbuf2;
+ need = zlog_one(zte, msgs[i], &state);
+ assert(need == 0);
+
+ if (!zte->sa_len)
+ ret = writev(fd, iov, state.iov - iov);
+ else {
+ mpos->msg_hdr.msg_iovlen = state.iov - iov;
+ ret = sendmsg(fd, &mpos->msg_hdr, 0);
+ }
+
+ if (ret < 0)
+ zlog_5424_err(zte, 1);
+ else
+ zlog_5424_err(zte, 0);
+
+ count = 0;
+ state.fbuf = &hdr_pos;
+ state.iov = iov;
+ mpos = mmsg;
+ }
+
+ assert(state.iov == iov);
+}
+
+/* strftime(), gmtime_r() and localtime_r() aren't AS-Safe (they access locale
+ * data), but we need an AS-Safe timestamp below :(
+ */
+static void gmtime_assafe(time_t ts, struct tm *res)
+{
+ res->tm_sec = ts % 60;
+ ts /= 60;
+ res->tm_min = ts % 60;
+ ts /= 60;
+ res->tm_hour = ts % 24;
+ ts /= 24;
+
+ ts -= 11017; /* start on 2020-03-01, 11017 days since 1970-01-01 */
+
+ /* 1461 days = 3 regular years + 1 leap year
+ * this works until 2100, which isn't a leap year
+ *
+ * struct tm.tm_year starts at 1900.
+ */
+ res->tm_year = 2000 - 1900 + 4 * (ts / 1461);
+ ts = ts % 1461;
+
+ if (ts == 1460) {
+ res->tm_year += 4;
+ res->tm_mon = 1;
+ res->tm_mday = 29;
+ return;
+ }
+ res->tm_year += ts / 365;
+ ts %= 365;
+
+ /* note we're starting in march like the romans did... */
+ if (ts >= 306) /* Jan 1 of next year */
+ res->tm_year++;
+
+ static unsigned int months[13] = {
+ 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337, 365,
+ };
+
+ for (size_t i = 0; i < array_size(months); i++) {
+ if ((unsigned int)ts < months[i + 1]) {
+ res->tm_mon = ((i + 2) % 12);
+ res->tm_mday = 1 + ts - months[i];
+ break;
+ }
+ }
+}
+
+/* one of the greatest advantages of this logging target: unlike syslog(),
+ * which is not AS-Safe, we can send crashlogs to syslog here.
+ */
+static void zlog_5424_sigsafe(struct zlog_target *zt, const char *text,
+ size_t len)
+{
+ static const char *const months_3164[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ };
+
+ struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt);
+ struct iovec iov[3], *iovp = iov;
+ char buf[256];
+ struct fbuf fbuf = {
+ .buf = buf,
+ .pos = buf,
+ .len = sizeof(buf),
+ };
+ int fd;
+ intmax_t pid = (intmax_t)getpid();
+ struct tm tm;
+
+ switch (zte->fmt) {
+ case ZLOG_FMT_5424:
+ gmtime_assafe(time(NULL), &tm);
+ bprintfrr(
+ &fbuf,
+ "<%d>1 %04u-%02u-%02uT%02u:%02u:%02uZ - %s %jd %.*s ",
+ zte->facility | LOG_CRIT, tm.tm_year + 1900,
+ tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
+ tm.tm_sec, zlog_progname, pid, (int)(zlog_prefixsz - 2),
+ zlog_prefix);
+ break;
+
+ case ZLOG_FMT_3164:
+ case ZLOG_FMT_LOCAL:
+ /* this will unfortuantely be wrong by the timezone offset
+ * if the user selected non-UTC. But not much we can do
+ * about that...
+ */
+ gmtime_assafe(time(NULL), &tm);
+ bprintfrr(&fbuf, "<%d>%3s %2u %02u:%02u:%02u %s%s[%jd]: ",
+ zte->facility | LOG_CRIT, months_3164[tm.tm_mon],
+ tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ (zte->fmt == ZLOG_FMT_LOCAL) ? "" : "- ",
+ zlog_progname, pid);
+ break;
+
+ case ZLOG_FMT_JOURNALD:
+ bprintfrr(&fbuf,
+ "PRIORITY=%d\n"
+ "SYSLOG_FACILITY=%d\n"
+ "FRR_DAEMON=%s\n"
+ "MESSAGE=",
+ LOG_CRIT, zte->facility, zlog_progname);
+ break;
+ }
+
+ iovp->iov_base = fbuf.buf;
+ iovp->iov_len = fbuf.pos - fbuf.buf;
+ iovp++;
+
+ iovp->iov_base = (char *)text;
+ iovp->iov_len = len;
+ iovp++;
+
+ if (zte->use_nl) {
+ iovp->iov_base = (char *)"\n";
+ iovp->iov_len = 1;
+ iovp++;
+ }
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+ if (!zte->sa_len)
+ writev(fd, iov, iovp - iov);
+ else {
+ struct msghdr mh = {};
+
+ mh.msg_name = (struct sockaddr *)&zte->sa;
+ mh.msg_namelen = zte->sa_len;
+ mh.msg_iov = iov;
+ mh.msg_iovlen = iovp - iov;
+ sendmsg(fd, &mh, 0);
+ }
+}
+
+/* housekeeping & configuration */
+
+void zlog_5424_init(struct zlog_cfg_5424 *zcf)
+{
+ pthread_mutex_init(&zcf->cfg_mtx, NULL);
+}
+
+static void zlog_5424_target_free(struct zlt_5424 *zlt)
+{
+ if (!zlt)
+ return;
+
+ rcu_close(&zlt->head_close, zlt->fd);
+ rcu_free(MTYPE_LOG_5424, zlt, zt.rcu_head);
+}
+
+void zlog_5424_fini(struct zlog_cfg_5424 *zcf, bool keepopen)
+{
+ if (keepopen)
+ zcf->active = NULL;
+
+ if (zcf->active) {
+ struct zlt_5424 *ztf;
+ struct zlog_target *zt;
+
+ zt = zlog_target_replace(&zcf->active->zt, NULL);
+ ztf = container_of(zt, struct zlt_5424, zt);
+ zlog_5424_target_free(ztf);
+ }
+ pthread_mutex_destroy(&zcf->cfg_mtx);
+}
+
+static void zlog_5424_cycle(struct zlog_cfg_5424 *zcf, int fd)
+{
+ struct zlog_target *old;
+ struct zlt_5424 *zlt = NULL, *oldt;
+
+ if (fd >= 0) {
+ struct zlog_target *zt;
+
+ /* all of this is swapped in by zlog_target_replace() below,
+ * the old target is RCU-freed afterwards.
+ */
+ zt = zlog_target_clone(MTYPE_LOG_5424, &zcf->active->zt,
+ sizeof(*zlt));
+ zlt = container_of(zt, struct zlt_5424, zt);
+
+ zlt->fd = fd;
+ zlt->kw_version = zcf->kw_version;
+ zlt->kw_location = zcf->kw_location;
+ zlt->kw_uid = zcf->kw_uid;
+ zlt->kw_ec = zcf->kw_ec;
+ zlt->kw_args = zcf->kw_args;
+ zlt->use_nl = true;
+ zlt->facility = zcf->facility;
+
+ /* DGRAM & SEQPACKET = 1 log message per packet */
+ zlt->packets = (zcf->sock_type == SOCK_DGRAM) ||
+ (zcf->sock_type == SOCK_SEQPACKET);
+ zlt->sa = zcf->sa;
+ zlt->sa_len = zcf->sa_len;
+ zlt->fmt = zcf->fmt;
+ zlt->zt.prio_min = zcf->prio_min;
+ zlt->zt.logfn = zlog_5424;
+ zlt->zt.logfn_sigsafe = zlog_5424_sigsafe;
+
+ switch (zcf->fmt) {
+ case ZLOG_FMT_5424:
+ case ZLOG_FMT_JOURNALD:
+ zlt->ts_flags = zcf->ts_flags;
+ zlt->ts_flags &= ZLOG_TS_PREC | ZLOG_TS_UTC;
+ zlt->ts_flags |= ZLOG_TS_ISO8601;
+ break;
+ case ZLOG_FMT_3164:
+ case ZLOG_FMT_LOCAL:
+ zlt->ts_flags = zcf->ts_flags & ZLOG_TS_UTC;
+ if (zlt->packets)
+ zlt->use_nl = false;
+ break;
+ }
+ }
+
+ old = zcf->active ? &zcf->active->zt : NULL;
+ old = zlog_target_replace(old, &zlt->zt);
+ zcf->active = zlt;
+
+ /* oldt->fd == fd happens for zlog_5424_apply_meta() */
+ oldt = container_of(old, struct zlt_5424, zt);
+ if (oldt && oldt->fd != (unsigned int)fd)
+ rcu_close(&oldt->head_close, oldt->fd);
+ rcu_free(MTYPE_LOG_5424, oldt, zt.rcu_head);
+}
+
+static int zlog_5424_reconnect(struct thread *t)
+{
+ struct zlog_cfg_5424 *zcf = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+ char dummy[256];
+ ssize_t ret;
+
+ if (zcf->active) {
+ ret = read(fd, dummy, sizeof(dummy));
+ if (ret > 0) {
+ /* logger is sending us something?!?! */
+ thread_add_read(t->master, zlog_5424_reconnect, zcf, fd,
+ &zcf->t_reconnect);
+ return 0;
+ }
+
+ if (ret == 0)
+ zlog_warn("logging socket %pSE closed by peer",
+ zcf->filename);
+ else
+ zlog_warn("logging socket %pSE error: %m",
+ zcf->filename);
+ }
+
+ /* do NOT close() anything here; other threads may still be writing
+ * and their messages need to be lost rather than end up on a random
+ * other fd that got reassigned the same number, like a BGP session!
+ */
+ fd = zlog_5424_open(zcf, -1);
+
+ frr_with_mutex (&zcf->cfg_mtx) {
+ zlog_5424_cycle(zcf, fd);
+ }
+ return 0;
+}
+
+static int zlog_5424_unix(struct sockaddr_un *suna, int sock_type)
+{
+ int fd;
+ int size = 1 * 1024 * 1024, prev_size;
+ socklen_t opt_size;
+ int save_errno;
+
+ fd = socket(AF_UNIX, sock_type, 0);
+ if (fd < 0)
+ return -1;
+
+ if (connect(fd, (struct sockaddr *)suna, sizeof(*suna))) {
+ /* zlog_5424_open() will print the error for connect() */
+ save_errno = errno;
+ close(fd);
+ errno = save_errno;
+ return -1;
+ }
+
+ opt_size = sizeof(prev_size);
+ if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &prev_size, &opt_size))
+ return fd;
+
+ /* setsockopt_so_sendbuf() logs on error; we don't really care that
+ * much here. Also, never shrink the buffer below the initial size.
+ */
+ while (size > prev_size &&
+ setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == -1)
+ size /= 2;
+
+ return fd;
+}
+
+static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type)
+{
+ int fd = -1;
+ int flags = 0;
+ int err;
+ socklen_t optlen;
+ bool do_chown = false;
+ bool need_reconnect = false;
+ static const int unix_types[] = {
+ SOCK_STREAM,
+ SOCK_SEQPACKET,
+ SOCK_DGRAM,
+ };
+ struct sockaddr_un sa;
+
+ zcf->sock_type = -1;
+ zcf->sa_len = 0;
+
+ switch (zcf->dst) {
+ case ZLOG_5424_DST_NONE:
+ break;
+
+ case ZLOG_5424_DST_FD:
+ fd = dup(zcf->fd);
+
+ optlen = sizeof(sock_type);
+ if (!getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &optlen)) {
+ zcf->sock_type = sock_type;
+ need_reconnect = (zcf->sock_type != SOCK_DGRAM);
+ }
+ break;
+
+ case ZLOG_5424_DST_FIFO:
+ if (!zcf->filename)
+ break;
+
+ if (!zcf->file_nocreate) {
+ frr_with_privs (lib_privs) {
+ mode_t prevmask;
+
+ prevmask = umask(0777 ^ zcf->file_mode);
+ err = mkfifo(zcf->filename, 0666);
+ umask(prevmask);
+ }
+ if (err == 0)
+ do_chown = true;
+ else if (errno != EEXIST)
+ break;
+ }
+
+ flags = O_NONBLOCK;
+ /* fallthru */
+
+ case ZLOG_5424_DST_FILE:
+ if (!zcf->filename)
+ break;
+
+ frr_with_privs (lib_privs) {
+ fd = open(zcf->filename, flags | O_WRONLY | O_APPEND |
+ O_CLOEXEC | O_NOCTTY);
+ }
+ if (fd >= 0)
+ break;
+ if (zcf->file_nocreate || flags) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "could not open log file %pSE: %m",
+ zcf->filename);
+ break;
+ }
+
+ frr_with_privs (lib_privs) {
+ mode_t prevmask;
+
+ prevmask = umask(0777 ^ zcf->file_mode);
+ fd = open(zcf->filename,
+ O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY |
+ O_CREAT | O_EXCL,
+ zcf->file_mode);
+ umask(prevmask);
+ }
+ if (fd >= 0) {
+ do_chown = true;
+ break;
+ }
+
+ frr_with_privs (lib_privs) {
+ fd = open(zcf->filename,
+ O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY);
+ }
+ if (fd >= 0)
+ break;
+
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "could not open or create log file %pSE: %m",
+ zcf->filename);
+ break;
+
+ case ZLOG_5424_DST_UNIX:
+ if (!zcf->filename)
+ break;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ strlcpy(sa.sun_path, zcf->filename, sizeof(sa.sun_path));
+
+ /* check if ZLOG_5424_DST_FD needs a touch when changing
+ * something here. the user can pass in a pre-opened unix
+ * socket through a fd at startup.
+ */
+ frr_with_privs (lib_privs) {
+ if (sock_type != -1)
+ fd = zlog_5424_unix(&sa, sock_type);
+ else {
+ for (size_t i = 0; i < array_size(unix_types);
+ i++) {
+ fd = zlog_5424_unix(&sa, unix_types[i]);
+ if (fd != -1) {
+ zcf->sock_type = unix_types[i];
+ break;
+ }
+ }
+ }
+ }
+ if (fd == -1) {
+ zcf->sock_type = -1;
+
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "could not connect to log unix path %pSE: %m",
+ zcf->filename);
+ need_reconnect = true;
+ } else {
+ /* datagram sockets are connectionless, restarting
+ * the receiver may lose some packets but will resume
+ * working afterwards without any action from us.
+ */
+ need_reconnect = (zcf->sock_type != SOCK_DGRAM);
+ }
+ break;
+ }
+
+ /* viable on both DST_FD and DST_UNIX path */
+ if (zcf->sock_type == SOCK_DGRAM) {
+ zcf->sa_len = sizeof(zcf->sa);
+ if (getpeername(fd, (struct sockaddr *)&zcf->sa,
+ &zcf->sa_len)) {
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "could not get remote address for log socket. logging may break if log receiver restarts.");
+ zcf->sa_len = 0;
+ }
+ }
+
+ if (do_chown) {
+ uid_t uid = zcf->file_uid;
+ gid_t gid = zcf->file_gid;
+
+ if (uid != (uid_t)-1 || gid != (gid_t)-1) {
+ frr_with_privs (lib_privs) {
+ err = fchown(fd, uid, gid);
+ }
+ if (err)
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "failed to chown() log file %pSE: %m",
+ zcf->filename);
+ }
+ }
+
+ if (need_reconnect) {
+ assert(zcf->master);
+
+ if (fd != -1) {
+ thread_add_read(zcf->master, zlog_5424_reconnect, zcf,
+ fd, &zcf->t_reconnect);
+ zcf->reconn_backoff_cur = zcf->reconn_backoff;
+
+ } else {
+ thread_add_timer_msec(zcf->master, zlog_5424_reconnect,
+ zcf, zcf->reconn_backoff_cur,
+ &zcf->t_reconnect);
+
+ zcf->reconn_backoff_cur += zcf->reconn_backoff_cur / 2;
+ if (zcf->reconn_backoff_cur > zcf->reconn_backoff_max)
+ zcf->reconn_backoff_cur =
+ zcf->reconn_backoff_max;
+ }
+ }
+
+ return fd;
+}
+
+bool zlog_5424_apply_dst(struct zlog_cfg_5424 *zcf)
+{
+ int fd = -1;
+
+ thread_cancel(&zcf->t_reconnect);
+
+ if (zcf->prio_min != ZLOG_DISABLED)
+ fd = zlog_5424_open(zcf, -1);
+
+ frr_with_mutex (&zcf->cfg_mtx) {
+ zlog_5424_cycle(zcf, fd);
+ }
+ return fd != -1;
+}
+
+
+bool zlog_5424_apply_meta(struct zlog_cfg_5424 *zcf)
+{
+ frr_with_mutex (&zcf->cfg_mtx) {
+ if (zcf->active)
+ zlog_5424_cycle(zcf, zcf->active->fd);
+ }
+
+ return true;
+}
+
+void zlog_5424_state(struct zlog_cfg_5424 *zcf, size_t *lost_msgs,
+ int *last_errno, bool *stale_errno, struct timeval *err_ts)
+{
+ if (lost_msgs)
+ *lost_msgs =
+ zcf->active
+ ? atomic_load_explicit(&zcf->active->lost_msgs,
+ memory_order_relaxed)
+ : 0;
+ if (last_errno)
+ *last_errno = zcf->active ? zcf->active->last_err : 0;
+ if (stale_errno)
+ *stale_errno = zcf->active ? !zcf->active->current_err : 0;
+ if (err_ts && zcf->active)
+ *err_ts = zcf->active->last_err_ts;
+}
+
+struct rcu_close_rotate {
+ struct rcu_head_close head_close;
+ struct rcu_head head_self;
+};
+
+bool zlog_5424_rotate(struct zlog_cfg_5424 *zcf)
+{
+ struct rcu_close_rotate *rcr;
+ int fd;
+
+ frr_with_mutex (&zcf->cfg_mtx) {
+ if (!zcf->active)
+ return true;
+
+ thread_cancel(&zcf->t_reconnect);
+
+ /* need to retain the socket type because it also influences
+ * other fields (packets) and we can't atomically swap these
+ * out. But we really want the atomic swap so we neither lose
+ * nor duplicate log messages, particularly for file targets.
+ *
+ * (zlog_5424_apply_dst / zlog_target_replace will cause
+ * duplicate log messages if another thread logs something
+ * while we're right in the middle of swapping out the log
+ * target)
+ */
+ fd = zlog_5424_open(zcf, zcf->sock_type);
+ if (fd < 0)
+ return false;
+
+ fd = atomic_exchange_explicit(&zcf->active->fd,
+ (uint_fast32_t)fd,
+ memory_order_relaxed);
+ }
+
+ rcr = XCALLOC(MTYPE_LOG_5424_ROTATE, sizeof(*rcr));
+ rcu_close(&rcr->head_close, fd);
+ rcu_free(MTYPE_LOG_5424_ROTATE, rcr, head_self);
+
+ return true;
+}
diff --git a/lib/zlog_5424.h b/lib/zlog_5424.h
new file mode 100644
index 0000000000..b4a12ccbff
--- /dev/null
+++ b/lib/zlog_5424.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2021 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_5424_H
+#define _FRR_ZLOG_5424_H
+
+#include <sys/stat.h>
+
+#include "typerb.h"
+#include "zlog.h"
+#include "zlog_targets.h"
+#include "qobj.h"
+
+struct thread;
+struct thread_master;
+
+enum zlog_5424_dst {
+ /* can be used to disable a target temporarily */
+ ZLOG_5424_DST_NONE = 0,
+
+ ZLOG_5424_DST_FD,
+ ZLOG_5424_DST_FILE,
+ ZLOG_5424_DST_FIFO,
+ ZLOG_5424_DST_UNIX,
+
+#define ZLOG_5424_DST_LAST ZLOG_5424_DST_UNIX
+};
+
+enum zlog_5424_format {
+ ZLOG_FMT_5424 = 0,
+ ZLOG_FMT_3164,
+ ZLOG_FMT_LOCAL,
+ ZLOG_FMT_JOURNALD,
+
+#define ZLOG_FMT_LAST ZLOG_FMT_JOURNALD
+};
+
+/* actual RCU'd logging backend */
+struct zlt_5424;
+
+struct zlog_cfg_5424 {
+ struct zlt_5424 *active;
+
+ pthread_mutex_t cfg_mtx;
+
+ /* general settings for all dsts */
+ int facility;
+ int prio_min;
+ bool kw_version;
+ bool kw_location;
+ bool kw_uid;
+ bool kw_ec;
+ bool kw_args;
+
+ uint32_t ts_flags;
+
+ enum zlog_5424_format fmt;
+
+ /* destination specifics */
+ enum zlog_5424_dst dst;
+
+ /* pre-opened FD. not the actual fd we log to */
+ int fd;
+
+ /* file, fifo, unix */
+ bool file_nocreate;
+
+ const char *filename;
+ mode_t file_mode;
+ /* -1 = no change */
+ uid_t file_uid;
+ gid_t file_gid;
+
+ /* remaining fields are internally used & updated by the 5424
+ * code - *not* config. don't set these.
+ */
+
+ /* sockets only - read handler to reconnect on errors */
+ struct thread_master *master;
+ struct thread *t_reconnect;
+ unsigned int reconn_backoff, reconn_backoff_cur, reconn_backoff_max;
+ int sock_type;
+ struct sockaddr_storage sa;
+ socklen_t sa_len;
+};
+
+/* these don't do malloc/free to allow using a static global */
+extern void zlog_5424_init(struct zlog_cfg_5424 *zcf);
+
+/* keepopen = true => for shutdown, just zap the config, keep logging */
+extern void zlog_5424_fini(struct zlog_cfg_5424 *zcf, bool keepopen);
+
+/* apply metadata/config changes */
+extern bool zlog_5424_apply_meta(struct zlog_cfg_5424 *zcf);
+
+/* apply changes requiring (re-)opening the destination
+ *
+ * also does log cycling/rotate & applies _meta at the same time
+ */
+extern bool zlog_5424_apply_dst(struct zlog_cfg_5424 *zcf);
+
+/* SIGHUP log rotation */
+extern bool zlog_5424_rotate(struct zlog_cfg_5424 *zcf);
+
+extern void zlog_5424_state(struct zlog_cfg_5424 *zcf, size_t *lost_msgs,
+ int *last_errno, bool *stale_errno,
+ struct timeval *err_ts);
+
+/* this is the dynamically allocated "variant" */
+PREDECL_RBTREE_UNIQ(targets);
+
+struct zlog_cfg_5424_user {
+ struct targets_item targets_item;
+ char *name;
+
+ struct zlog_cfg_5424 cfg;
+
+ char *envvar;
+
+ /* non-const, always same as cfg.filename */
+ char *filename;
+
+ /* uid/gid strings to write back out in show config */
+ char *file_user;
+ char *file_group;
+
+ bool reconf_dst;
+ bool reconf_meta;
+
+ int unix_special;
+
+ QOBJ_FIELDS;
+};
+
+DECLARE_QOBJ_TYPE(zlog_cfg_5424_user);
+
+extern void log_5424_cmd_init(void);
+
+#endif /* _FRR_ZLOG_5424_H */
diff --git a/lib/zlog_5424_cli.c b/lib/zlog_5424_cli.c
new file mode 100644
index 0000000000..dd8dbfaffd
--- /dev/null
+++ b/lib/zlog_5424_cli.c
@@ -0,0 +1,1006 @@
+/*
+ * Copyright (C) 2021 David Lamparter for NetDEF, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "zebra.h"
+#include "zlog_5424.h"
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "lib/command.h"
+#include "lib/libfrr.h"
+#include "lib/log_vty.h"
+
+DEFINE_MTYPE_STATIC(LOG, LOG_5424_CONFIG, "extended syslog config");
+DEFINE_MTYPE_STATIC(LOG, LOG_5424_DATA, "extended syslog config items");
+
+static int target_cmp(const struct zlog_cfg_5424_user *a,
+ const struct zlog_cfg_5424_user *b)
+{
+ return strcmp(a->name, b->name);
+}
+
+DECLARE_RBTREE_UNIQ(targets, struct zlog_cfg_5424_user, targets_item,
+ target_cmp);
+DEFINE_QOBJ_TYPE(zlog_cfg_5424_user);
+
+static struct targets_head targets = INIT_RBTREE_UNIQ(targets);
+static struct thread_master *log_5424_master;
+
+static void clear_dst(struct zlog_cfg_5424_user *cfg);
+
+struct log_option {
+ const char *name;
+ ptrdiff_t offs;
+ bool dflt;
+};
+
+/* clang-format off */
+static struct log_option log_opts[] = {
+ { "code-location", offsetof(struct zlog_cfg_5424, kw_location) },
+ { "version", offsetof(struct zlog_cfg_5424, kw_version) },
+ { "unique-id", offsetof(struct zlog_cfg_5424, kw_uid), true },
+ { "error-category", offsetof(struct zlog_cfg_5424, kw_ec), true },
+ { "format-args", offsetof(struct zlog_cfg_5424, kw_args) },
+ {},
+};
+
+#define DFLT_TS_FLAGS (6 | ZLOG_TS_UTC)
+#define DFLT_FACILITY LOG_DAEMON
+#define DFLT_PRIO_MIN LOG_DEBUG
+/* clang-format on */
+
+enum unix_special {
+ SPECIAL_NONE = 0,
+ SPECIAL_SYSLOG,
+ SPECIAL_JOURNALD,
+};
+
+static struct zlog_cfg_5424_user *log_5424_alloc(const char *name)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ cfg = XCALLOC(MTYPE_LOG_5424_CONFIG, sizeof(*cfg));
+ cfg->name = XSTRDUP(MTYPE_LOG_5424_DATA, name);
+
+ cfg->cfg.master = log_5424_master;
+ cfg->cfg.kw_location = true;
+ cfg->cfg.kw_version = false;
+ cfg->cfg.facility = DFLT_FACILITY;
+ cfg->cfg.prio_min = DFLT_PRIO_MIN;
+ cfg->cfg.ts_flags = DFLT_TS_FLAGS;
+ clear_dst(cfg);
+
+ for (struct log_option *opt = log_opts; opt->name; opt++) {
+ bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs);
+ *ptr = opt->dflt;
+ }
+
+ zlog_5424_init(&cfg->cfg);
+
+ QOBJ_REG(cfg, zlog_cfg_5424_user);
+ targets_add(&targets, cfg);
+ return cfg;
+}
+
+static void log_5424_free(struct zlog_cfg_5424_user *cfg, bool keepopen)
+{
+ targets_del(&targets, cfg);
+ QOBJ_UNREG(cfg);
+
+ zlog_5424_fini(&cfg->cfg, keepopen);
+ clear_dst(cfg);
+
+ XFREE(MTYPE_LOG_5424_DATA, cfg->filename);
+ XFREE(MTYPE_LOG_5424_DATA, cfg->name);
+ XFREE(MTYPE_LOG_5424_CONFIG, cfg);
+}
+
+static void clear_dst(struct zlog_cfg_5424_user *cfg)
+{
+ XFREE(MTYPE_LOG_5424_DATA, cfg->filename);
+ cfg->cfg.filename = cfg->filename;
+
+ XFREE(MTYPE_LOG_5424_DATA, cfg->file_user);
+ XFREE(MTYPE_LOG_5424_DATA, cfg->file_group);
+ XFREE(MTYPE_LOG_5424_DATA, cfg->envvar);
+
+ cfg->cfg.fd = -1;
+ cfg->cfg.file_uid = -1;
+ cfg->cfg.file_gid = -1;
+ cfg->cfg.file_mode = LOGFILE_MASK & 0666;
+ cfg->cfg.file_nocreate = false;
+ cfg->cfg.dst = ZLOG_5424_DST_NONE;
+}
+
+static int reconf_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty)
+{
+ if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE)
+ vty_out(vty,
+ "%% Changes will be applied when exiting this config block\n");
+
+ cfg->reconf_dst = true;
+ return CMD_SUCCESS;
+}
+
+static int reconf_meta(struct zlog_cfg_5424_user *cfg, struct vty *vty)
+{
+ if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE)
+ vty_out(vty,
+ "%% Changes will be applied when exiting this config block\n");
+
+ cfg->reconf_meta = true;
+ return CMD_SUCCESS;
+}
+
+static int reconf_clear_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty)
+{
+ if (cfg->cfg.dst == ZLOG_5424_DST_NONE)
+ return CMD_SUCCESS;
+
+ clear_dst(cfg);
+ return reconf_dst(cfg, vty);
+}
+
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/zlog_5424_cli_clippy.c"
+#endif
+
+DEFPY_NOSH(log_5424_target,
+ log_5424_target_cmd,
+ "log extended-syslog EXTLOGNAME",
+ "Logging control\n"
+ "Extended RFC5424 syslog (including file targets)\n"
+ "Name identifying this syslog target\n")
+{
+ struct zlog_cfg_5424_user *cfg, ref;
+
+ ref.name = (char *)extlogname;
+ cfg = targets_find(&targets, &ref);
+
+ if (!cfg)
+ cfg = log_5424_alloc(extlogname);
+
+ VTY_PUSH_CONTEXT(EXTLOG_NODE, cfg);
+ return CMD_SUCCESS;
+}
+
+DEFPY(no_log_5424_target,
+ no_log_5424_target_cmd,
+ "no log extended-syslog EXTLOGNAME",
+ NO_STR
+ "Logging control\n"
+ "Extended RFC5424 syslog (including file targets)\n"
+ "Name identifying this syslog target\n")
+{
+ struct zlog_cfg_5424_user *cfg, ref;
+
+ ref.name = (char *)extlogname;
+ cfg = targets_find(&targets, &ref);
+
+ if (!cfg) {
+ vty_out(vty, "%% No extended syslog target named \"%s\"\n",
+ extlogname);
+ return CMD_WARNING;
+ }
+
+ log_5424_free(cfg, false);
+ return CMD_SUCCESS;
+}
+
+/* "format <rfc3164|rfc5424|local-syslogd|journald>$fmt" */
+#define FORMAT_HELP \
+ "Select log message formatting\n" \
+ "RFC3164 (legacy) syslog\n" \
+ "RFC5424 (modern) syslog, supports structured data (default)\n" \
+ "modified RFC3164 without hostname for local syslogd (/dev/log)\n" \
+ "journald (systemd log) native format\n" \
+ /* end */
+
+static enum zlog_5424_format log_5424_fmt(const char *fmt,
+ enum zlog_5424_format dflt)
+{
+ if (!fmt)
+ return dflt;
+ else if (!strcmp(fmt, "rfc5424"))
+ return ZLOG_FMT_5424;
+ else if (!strcmp(fmt, "rfc3164"))
+ return ZLOG_FMT_3164;
+ else if (!strcmp(fmt, "local-syslogd"))
+ return ZLOG_FMT_LOCAL;
+ else if (!strcmp(fmt, "journald"))
+ return ZLOG_FMT_JOURNALD;
+
+ return dflt;
+}
+
+DEFPY(log_5424_destination_file,
+ log_5424_destination_file_cmd,
+ "[no] destination file$type PATH "
+ "[create$create [{user WORD|group WORD|mode PERMS}]"
+ "|no-create$nocreate] "
+ "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]",
+ NO_STR
+ "Log destination setup\n"
+ "Log to file\n"
+ "Path to destination\n"
+ "Create file if it does not exist\n"
+ "Set file owner\n"
+ "User name\n"
+ "Set file group\n"
+ "Group name\n"
+ "Set permissions\n"
+ "File permissions (octal)\n"
+ "Do not create file if it does not exist\n"
+ FORMAT_HELP)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ enum zlog_5424_dst dst;
+ bool reconf = true, warn_perm = false;
+ char *prev_user, *prev_group;
+ mode_t perm_val = LOGFILE_MASK & 0666;
+ enum zlog_5424_format fmtv;
+
+ if (no)
+ return reconf_clear_dst(cfg, vty);
+
+ fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424);
+
+ if (perms) {
+ char *errp = (char *)perms;
+
+ perm_val = strtoul(perms, &errp, 8);
+ if (*errp || errp == perms || perm_val == 0 ||
+ (perm_val & ~0666)) {
+ vty_out(vty, "%% Invalid permissions value \"%s\"\n",
+ perms);
+ return CMD_WARNING;
+ }
+ }
+
+ dst = (strcmp(type, "fifo") == 0) ? ZLOG_5424_DST_FIFO
+ : ZLOG_5424_DST_FILE;
+
+ if (cfg->filename && !strcmp(path, cfg->filename) &&
+ dst == cfg->cfg.dst && cfg->cfg.active && cfg->cfg.fmt == fmtv)
+ reconf = false;
+
+ /* keep for compare below */
+ prev_user = cfg->file_user;
+ prev_group = cfg->file_group;
+ cfg->file_user = NULL;
+ cfg->file_group = NULL;
+
+ clear_dst(cfg);
+
+ cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path);
+ cfg->cfg.dst = dst;
+ cfg->cfg.filename = cfg->filename;
+ cfg->cfg.fmt = fmtv;
+
+ if (nocreate)
+ cfg->cfg.file_nocreate = true;
+ else {
+ if (user) {
+ struct passwd *pwent;
+
+ warn_perm |= (prev_user && strcmp(user, prev_user));
+ cfg->file_user = XSTRDUP(MTYPE_LOG_5424_DATA, user);
+
+ errno = 0;
+ pwent = getpwnam(user);
+ if (!pwent)
+ vty_out(vty,
+ "%% Could not look up user \"%s\" (%s), file owner will be left untouched!\n",
+ user,
+ errno ? safe_strerror(errno)
+ : "No entry by this user name");
+ else
+ cfg->cfg.file_uid = pwent->pw_uid;
+ }
+ if (group) {
+ struct group *grent;
+
+ warn_perm |= (prev_group && strcmp(group, prev_group));
+ cfg->file_group = XSTRDUP(MTYPE_LOG_5424_DATA, group);
+
+ errno = 0;
+ grent = getgrnam(group);
+ if (!grent)
+ vty_out(vty,
+ "%% Could not look up group \"%s\" (%s), file group will be left untouched!\n",
+ group,
+ errno ? safe_strerror(errno)
+ : "No entry by this group name");
+ else
+ cfg->cfg.file_gid = grent->gr_gid;
+ }
+ }
+ XFREE(MTYPE_LOG_5424_DATA, prev_user);
+ XFREE(MTYPE_LOG_5424_DATA, prev_group);
+
+ if (cfg->cfg.file_uid != (uid_t)-1 || cfg->cfg.file_gid != (gid_t)-1) {
+ struct stat st;
+
+ if (stat(cfg->filename, &st) == 0) {
+ warn_perm |= (st.st_uid != cfg->cfg.file_uid);
+ warn_perm |= (st.st_gid != cfg->cfg.file_gid);
+ }
+ }
+ if (warn_perm)
+ vty_out(vty,
+ "%% Warning: ownership and permission bits are only applied when creating\n"
+ "%% log files. Use system tools to change existing files.\n"
+ "%% FRR may also be missing necessary privileges to set these.\n");
+
+ if (reconf)
+ return reconf_dst(cfg, vty);
+
+ return CMD_SUCCESS;
+}
+
+/* FIFOs are for legacy /dev/log implementations; using this is very much not
+ * recommended since it can unexpectedly block in logging calls. Also the fd
+ * would need to be reopened when the process at the other end restarts. None
+ * of this is handled - use at your own caution. It's _HIDDEN for a purpose.
+ */
+ALIAS_HIDDEN(log_5424_destination_file,
+ log_5424_destination_fifo_cmd,
+ "[no] destination fifo$type PATH "
+ "[create$create [{owner WORD|group WORD|permissions PERMS}]"
+ "|no-create$nocreate] "
+ "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]",
+ NO_STR
+ "Log destination setup\n"
+ "Log to filesystem FIFO\n"
+ "Path to destination\n"
+ "Create file if it does not exist\n"
+ "Set file owner\n"
+ "User name\n"
+ "Set file group\n"
+ "Group name\n"
+ "Set permissions\n"
+ "File permissions (octal)\n"
+ "Do not create file if it does not exist\n"
+ FORMAT_HELP)
+
+static int dst_unix(struct vty *vty, const char *no, const char *path,
+ enum zlog_5424_format fmt, enum unix_special special)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+
+ if (no)
+ return reconf_clear_dst(cfg, vty);
+
+ cfg->unix_special = special;
+
+ if (cfg->cfg.dst == ZLOG_5424_DST_UNIX && cfg->filename &&
+ !strcmp(path, cfg->filename) && cfg->cfg.active &&
+ cfg->cfg.fmt == fmt)
+ return CMD_SUCCESS;
+
+ clear_dst(cfg);
+
+ cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path);
+ cfg->cfg.dst = ZLOG_5424_DST_UNIX;
+ cfg->cfg.filename = cfg->filename;
+ cfg->cfg.fmt = fmt;
+
+ cfg->cfg.reconn_backoff = 25;
+ cfg->cfg.reconn_backoff_cur = 25;
+ cfg->cfg.reconn_backoff_max = 10000;
+ return reconf_dst(cfg, vty);
+}
+
+DEFPY(log_5424_destination_unix,
+ log_5424_destination_unix_cmd,
+ "[no] destination unix PATH "
+ "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]",
+ NO_STR
+ "Log destination setup\n"
+ "Log to unix socket\n"
+ "Unix socket path\n"
+ FORMAT_HELP)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ enum zlog_5424_format fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424);
+
+ return dst_unix(vty, no, path, fmtv, SPECIAL_NONE);
+}
+
+DEFPY(log_5424_destination_journald,
+ log_5424_destination_journald_cmd,
+ "[no] destination journald",
+ NO_STR
+ "Log destination setup\n"
+ "Log directly to systemd's journald\n")
+{
+ return dst_unix(vty, no, "/run/systemd/journal/socket",
+ ZLOG_FMT_JOURNALD, SPECIAL_JOURNALD);
+}
+
+#if defined(__FreeBSD_version) && (__FreeBSD_version >= 1200061)
+#define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424
+#elif defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 500000000)
+#define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424
+#else
+#define ZLOG_FMT_DEV_LOG ZLOG_FMT_LOCAL
+#endif
+
+DEFPY(log_5424_destination_syslog,
+ log_5424_destination_syslog_cmd,
+ "[no] destination syslog [supports-rfc5424]$supp5424",
+ NO_STR
+ "Log destination setup\n"
+ "Log directly to syslog\n"
+ "Use RFC5424 format (please refer to documentation)\n")
+{
+ int format = supp5424 ? ZLOG_FMT_5424 : ZLOG_FMT_DEV_LOG;
+
+ /* unfortunately, there is no way to detect 5424 support */
+ return dst_unix(vty, no, "/dev/log", format, SPECIAL_SYSLOG);
+}
+
+/* could add something like
+ * "destination <udp|tcp>$proto <A.B.C.D|X:X::X:X> (1-65535)$port"
+ * here, but there are 2 reasons not to do that:
+ *
+ * - each FRR daemon would open its own connection, there's no system level
+ * aggregation. That's the system's syslogd's job. It likely also
+ * supports directing & filtering log messages with configurable rules.
+ * - we're likely not going to support DTLS or TLS for more secure logging;
+ * adding this would require a considerable amount of additional config
+ * and an entire TLS library to begin with. A proper syslogd implements
+ * all of this, why reinvent the wheel?
+ */
+
+DEFPY(log_5424_destination_fd,
+ log_5424_destination_fd_cmd,
+ "[no] destination <fd <(0-63)$fd|envvar WORD>|stdout$fd1|stderr$fd2>"
+ "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]",
+ NO_STR
+ "Log destination setup\n"
+ "Log to pre-opened file descriptor\n"
+ "File descriptor number (must be open at startup)\n"
+ "Read file descriptor number from environment variable\n"
+ "Environment variable name\n"
+ "Log to standard output\n"
+ "Log to standard error output\n"
+ FORMAT_HELP)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ bool envvar_problem = false;
+ enum zlog_5424_format fmtv;
+
+ if (no)
+ return reconf_clear_dst(cfg, vty);
+
+ fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424);
+
+ if (envvar) {
+ char *envval;
+
+ envval = getenv(envvar);
+ if (!envval)
+ envvar_problem = true;
+ else {
+ char *errp = envval;
+
+ fd = strtoul(envval, &errp, 0);
+ if (errp == envval || *errp)
+ envvar_problem = true;
+ }
+
+ if (envvar_problem)
+ fd = -1;
+ } else if (fd1)
+ fd = 1;
+ else if (fd2)
+ fd = 2;
+
+ if (cfg->cfg.dst == ZLOG_5424_DST_FD && cfg->cfg.fd == fd &&
+ cfg->cfg.active && cfg->cfg.fmt == fmtv)
+ return CMD_SUCCESS;
+
+ clear_dst(cfg);
+
+ cfg->cfg.dst = ZLOG_5424_DST_FD;
+ cfg->cfg.fd = fd;
+ cfg->cfg.fmt = fmtv;
+ if (envvar)
+ cfg->envvar = XSTRDUP(MTYPE_LOG_5424_DATA, envvar);
+
+ if (envvar_problem)
+ vty_out(vty,
+ "%% environment variable \"%s\" not present or invalid.\n",
+ envvar);
+ if (!frr_is_startup_fd(fd))
+ vty_out(vty,
+ "%% file descriptor %d was not open when this process was started\n",
+ (int)fd);
+ if (envvar_problem || !frr_is_startup_fd(fd))
+ vty_out(vty,
+ "%% configuration will be saved but has no effect currently\n");
+
+ return reconf_dst(cfg, vty);
+}
+
+DEFPY(log_5424_destination_none,
+ log_5424_destination_none_cmd,
+ "[no] destination [none]",
+ NO_STR
+ "Log destination setup\n"
+ "Deconfigure destination\n")
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+
+ return reconf_clear_dst(cfg, vty);
+}
+
+/* end of destinations */
+
+DEFPY(log_5424_prio,
+ log_5424_prio_cmd,
+ "[no] priority <emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>$levelarg",
+ NO_STR
+ "Set minimum message priority to include for this target\n"
+ LOG_LEVEL_DESC)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ int prio_min = log_level_match(levelarg);
+
+ if (prio_min == cfg->cfg.prio_min)
+ return CMD_SUCCESS;
+
+ cfg->cfg.prio_min = prio_min;
+ return reconf_meta(cfg, vty);
+}
+
+DEFPY(log_5424_facility,
+ log_5424_facility_cmd,
+ "[no] facility <kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7>$facilityarg",
+ NO_STR
+ "Set syslog facility to use\n"
+ LOG_FACILITY_DESC)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ int facility = facility_match(facilityarg);
+
+ if (cfg->cfg.facility == facility)
+ return CMD_SUCCESS;
+
+ cfg->cfg.facility = facility;
+ return reconf_meta(cfg, vty);
+}
+
+DEFPY(log_5424_meta,
+ log_5424_meta_cmd,
+ "[no] structured-data <code-location|version|unique-id|error-category|format-args>$option",
+ NO_STR
+ "Select structured data (key/value pairs) to include in each message\n"
+ "FRR source code location\n"
+ "FRR version\n"
+ "Unique message identifier (XXXXX-XXXXX)\n"
+ "Error category (EC numeric)\n"
+ "Individual formatted log message arguments\n")
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ bool val = !no, *ptr;
+ struct log_option *opt = log_opts;
+
+ while (opt->name && strcmp(opt->name, option))
+ opt++;
+ if (!opt->name)
+ return CMD_WARNING;
+
+ ptr = (bool *)(((char *)&cfg->cfg) + opt->offs);
+ if (*ptr == val)
+ return CMD_SUCCESS;
+
+ *ptr = val;
+ return reconf_meta(cfg, vty);
+}
+
+DEFPY(log_5424_ts_prec,
+ log_5424_ts_prec_cmd,
+ "[no] timestamp precision (0-9)",
+ NO_STR
+ "Timestamp options\n"
+ "Number of sub-second digits to include\n"
+ "Number of sub-second digits to include\n")
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ uint32_t ts_flags = cfg->cfg.ts_flags;
+
+ ts_flags &= ~ZLOG_TS_PREC;
+ if (no)
+ ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_PREC;
+ else
+ ts_flags |= precision;
+
+ if (ts_flags == cfg->cfg.ts_flags)
+ return CMD_SUCCESS;
+
+ cfg->cfg.ts_flags = ts_flags;
+ return reconf_meta(cfg, vty);
+}
+
+DEFPY(log_5424_ts_local,
+ log_5424_ts_local_cmd,
+ "[no] timestamp local-time",
+ NO_STR
+ "Timestamp options\n"
+ "Use local system time zone rather than UTC\n")
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ uint32_t ts_flags = cfg->cfg.ts_flags;
+
+ ts_flags &= ~ZLOG_TS_UTC;
+ if (no)
+ ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_UTC;
+ else
+ ts_flags |= (~DFLT_TS_FLAGS) & ZLOG_TS_UTC;
+
+ if (ts_flags == cfg->cfg.ts_flags)
+ return CMD_SUCCESS;
+
+ cfg->cfg.ts_flags = ts_flags;
+ return reconf_meta(cfg, vty);
+}
+
+static int log_5424_node_exit(struct vty *vty)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+
+ if ((cfg->reconf_dst || cfg->reconf_meta) && vty->type != VTY_FILE)
+ vty_out(vty, "%% applying changes.\n");
+
+ if (cfg->reconf_dst)
+ zlog_5424_apply_dst(&cfg->cfg);
+ else if (cfg->reconf_meta)
+ zlog_5424_apply_meta(&cfg->cfg);
+
+ cfg->reconf_dst = cfg->reconf_meta = false;
+ return 1;
+}
+
+static int log_5424_config_write(struct vty *vty)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ frr_each (targets, &targets, cfg) {
+ const char *fmt_str = "";
+
+ vty_out(vty, "log extended %s\n", cfg->name);
+
+ switch (cfg->cfg.fmt) {
+ case ZLOG_FMT_5424:
+ fmt_str = " format rfc5424";
+ break;
+ case ZLOG_FMT_3164:
+ fmt_str = " format rfc3164";
+ break;
+ case ZLOG_FMT_LOCAL:
+ fmt_str = " format local-syslogd";
+ break;
+ case ZLOG_FMT_JOURNALD:
+ fmt_str = " format journald";
+ break;
+ }
+
+ switch (cfg->cfg.dst) {
+ case ZLOG_5424_DST_NONE:
+ vty_out(vty, " ! no destination configured\n");
+ break;
+
+ case ZLOG_5424_DST_FD:
+ if (cfg->cfg.fmt == ZLOG_FMT_5424)
+ fmt_str = "";
+
+ if (cfg->envvar)
+ vty_out(vty, " destination fd envvar %s%s\n",
+ cfg->envvar, fmt_str);
+ else if (cfg->cfg.fd == 1)
+ vty_out(vty, " destination stdout%s\n",
+ fmt_str);
+ else if (cfg->cfg.fd == 2)
+ vty_out(vty, " destination stderr%s\n",
+ fmt_str);
+ else
+ vty_out(vty, " destination fd %d%s\n",
+ cfg->cfg.fd, fmt_str);
+ break;
+
+ case ZLOG_5424_DST_FILE:
+ case ZLOG_5424_DST_FIFO:
+ if (cfg->cfg.fmt == ZLOG_FMT_5424)
+ fmt_str = "";
+
+ vty_out(vty, " destination %s %s",
+ (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo"
+ : "file",
+ cfg->filename);
+
+ if (cfg->cfg.file_nocreate)
+ vty_out(vty, " no-create");
+ else if (cfg->file_user || cfg->file_group ||
+ cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) {
+ vty_out(vty, " create");
+
+ if (cfg->file_user)
+ vty_out(vty, " user %s",
+ cfg->file_user);
+ if (cfg->file_group)
+ vty_out(vty, " group %s",
+ cfg->file_group);
+ if (cfg->cfg.file_mode != (LOGFILE_MASK & 0666))
+ vty_out(vty, " mode %04o",
+ cfg->cfg.file_mode);
+ }
+ vty_out(vty, "%s\n", fmt_str);
+ break;
+
+ case ZLOG_5424_DST_UNIX:
+ switch (cfg->unix_special) {
+ case SPECIAL_NONE:
+ vty_out(vty, " destination unix %s%s\n",
+ cfg->filename, fmt_str);
+ break;
+ case SPECIAL_SYSLOG:
+ if (cfg->cfg.fmt == ZLOG_FMT_DEV_LOG)
+ vty_out(vty, " destination syslog\n");
+ else
+ vty_out(vty,
+ " destination syslog supports-rfc5424\n");
+ break;
+ case SPECIAL_JOURNALD:
+ vty_out(vty, " destination journald\n");
+ break;
+ }
+ break;
+ }
+
+ if (cfg->cfg.prio_min != LOG_DEBUG)
+ vty_out(vty, " priority %s\n",
+ zlog_priority_str(cfg->cfg.prio_min));
+ if (cfg->cfg.facility != DFLT_FACILITY)
+ vty_out(vty, " facility %s\n",
+ facility_name(cfg->cfg.facility));
+
+ for (struct log_option *opt = log_opts; opt->name; opt++) {
+ bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs);
+
+ if (*ptr != opt->dflt)
+ vty_out(vty, " %sstructured-data %s\n",
+ *ptr ? "" : "no ", opt->name);
+ }
+
+ if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_PREC)
+ vty_out(vty, " timestamp precision %u\n",
+ cfg->cfg.ts_flags & ZLOG_TS_PREC);
+
+ if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_UTC) {
+ if (cfg->cfg.ts_flags & ZLOG_TS_UTC)
+ vty_out(vty, " no timestamp local-time\n");
+ else
+ vty_out(vty, " timestamp local-time\n");
+ }
+
+ vty_out(vty, "!\n");
+ }
+ return 0;
+}
+
+static int log_5424_show(struct vty *vty)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ frr_each (targets, &targets, cfg) {
+ vty_out(vty, "\nExtended log target %pSQq\n", cfg->name);
+
+ switch (cfg->cfg.dst) {
+ case ZLOG_5424_DST_NONE:
+ vty_out(vty,
+ " Inactive (no destination configured)\n");
+ break;
+
+ case ZLOG_5424_DST_FD:
+ if (cfg->envvar)
+ vty_out(vty,
+ " logging to fd %d from environment variable %pSE\n",
+ cfg->cfg.fd, cfg->envvar);
+ else if (cfg->cfg.fd == 1)
+ vty_out(vty, " logging to stdout\n");
+ else if (cfg->cfg.fd == 2)
+ vty_out(vty, " logging to stderr\n");
+ else
+ vty_out(vty, " logging to fd %d\n",
+ cfg->cfg.fd);
+ break;
+
+ case ZLOG_5424_DST_FILE:
+ case ZLOG_5424_DST_FIFO:
+ case ZLOG_5424_DST_UNIX:
+ vty_out(vty, " logging to %s: %pSE\n",
+ (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo"
+ : (cfg->cfg.dst == ZLOG_5424_DST_UNIX)
+ ? "unix socket"
+ : "file",
+ cfg->filename);
+ break;
+ }
+
+ vty_out(vty, " log level: %s, facility: %s\n",
+ zlog_priority_str(cfg->cfg.prio_min),
+ facility_name(cfg->cfg.facility));
+
+ bool any_meta = false, first = true;
+
+ for (struct log_option *opt = log_opts; opt->name; opt++) {
+ bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs);
+
+ any_meta |= *ptr;
+ }
+
+ if (!any_meta)
+ continue;
+
+ switch (cfg->cfg.fmt) {
+ case ZLOG_FMT_5424:
+ case ZLOG_FMT_JOURNALD:
+ vty_out(vty, " structured data: ");
+
+ for (struct log_option *opt = log_opts; opt->name;
+ opt++) {
+ bool *ptr = (bool *)(((char *)&cfg->cfg) +
+ opt->offs);
+
+ if (*ptr) {
+ vty_out(vty, "%s%s", first ? "" : ", ",
+ opt->name);
+ first = false;
+ }
+ }
+ break;
+
+ default:
+ vty_out(vty,
+ " structured data is not supported by the selected format\n");
+ break;
+ }
+
+ vty_out(vty, "\n");
+
+ size_t lost_msgs;
+ int last_errno;
+ bool stale_errno;
+ struct timeval err_ts;
+ int64_t since;
+
+ zlog_5424_state(&cfg->cfg, &lost_msgs, &last_errno,
+ &stale_errno, &err_ts);
+ vty_out(vty, " number of lost messages: %zu\n", lost_msgs);
+
+ if (last_errno == 0)
+ since = 0;
+ else
+ since = monotime_since(&err_ts, NULL);
+ vty_out(vty,
+ " last error: %s (%lld.%06llds ago, currently %s)\n",
+ last_errno ? safe_strerror(last_errno) : "none",
+ since / 1000000LL, since % 1000000LL,
+ stale_errno ? "OK" : "erroring");
+ }
+ return 0;
+}
+
+static struct cmd_node extlog_node = {
+ .name = "extended",
+ .node = EXTLOG_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-ext-log)# ",
+
+ .config_write = log_5424_config_write,
+ .node_exit = log_5424_node_exit,
+};
+
+static void log_5424_autocomplete(vector comps, struct cmd_token *token)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ frr_each (targets, &targets, cfg)
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, cfg->name));
+}
+
+static const struct cmd_variable_handler log_5424_var_handlers[] = {
+ {.tokenname = "EXTLOGNAME", .completions = log_5424_autocomplete},
+ {.completions = NULL},
+};
+
+void log_5424_cmd_init(void)
+{
+ hook_register(zlog_cli_show, log_5424_show);
+
+ cmd_variable_handler_register(log_5424_var_handlers);
+
+ /* CLI commands. */
+ install_node(&extlog_node);
+ install_default(EXTLOG_NODE);
+
+ install_element(CONFIG_NODE, &log_5424_target_cmd);
+ install_element(CONFIG_NODE, &no_log_5424_target_cmd);
+
+ install_element(EXTLOG_NODE, &log_5424_destination_file_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_fifo_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_unix_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_journald_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_syslog_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_fd_cmd);
+
+ install_element(EXTLOG_NODE, &log_5424_meta_cmd);
+ install_element(EXTLOG_NODE, &log_5424_prio_cmd);
+ install_element(EXTLOG_NODE, &log_5424_facility_cmd);
+ install_element(EXTLOG_NODE, &log_5424_ts_prec_cmd);
+ install_element(EXTLOG_NODE, &log_5424_ts_local_cmd);
+}
+
+/* hooks */
+
+static int log_5424_early_init(struct thread_master *master);
+static int log_5424_rotate(void);
+static int log_5424_fini(void);
+
+__attribute__((_CONSTRUCTOR(475))) static void zlog_5424_startup_init(void)
+{
+ hook_register(frr_early_init, log_5424_early_init);
+ hook_register(zlog_rotate, log_5424_rotate);
+ hook_register(frr_fini, log_5424_fini);
+}
+
+static int log_5424_early_init(struct thread_master *master)
+{
+ log_5424_master = master;
+
+ return 0;
+}
+
+static int log_5424_rotate(void)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ frr_each (targets, &targets, cfg)
+ if (!zlog_5424_rotate(&cfg->cfg))
+ zlog_err(
+ "log rotation on extended log target %s failed",
+ cfg->name);
+
+ return 0;
+}
+
+static int log_5424_fini(void)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ while ((cfg = targets_pop(&targets)))
+ log_5424_free(cfg, true);
+
+ log_5424_master = NULL;
+
+ return 0;
+}