[systemd-devel] [PATCH] journald: Introduce RFC 5424 syslog

Susant Sahani susant at redhat.com
Wed Feb 18 23:58:39 PST 2015


This patch adds support for RFC 5424 syslog format to journald. Journald
can now forward logs to a multicast UDP group.

RFC 5424 format:
<PRI>VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP
[SD-ID]s SP MSG

Example conf:

file: journald.conf
SysLogAddress=239.0.0.1:6000
---
 Makefile.am                           |   1 +
 man/journald.conf.xml                 |  12 ++
 src/journal/journald-gperf.gperf      |   1 +
 src/journal/journald-native.c         |   3 +
 src/journal/journald-server.c         |  40 +++++-
 src/journal/journald-server.h         |  14 ++
 src/journal/journald-stream.c         |   4 +
 src/journal/journald-syslog-network.c | 246 ++++++++++++++++++++++++++++++++++
 src/journal/journald-syslog.c         |   3 +
 src/journal/journald-syslog.h         |   2 +
 10 files changed, 325 insertions(+), 1 deletion(-)
 create mode 100644 src/journal/journald-syslog-network.c

diff --git a/Makefile.am b/Makefile.am
index ba63f68..b015f69 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4487,6 +4487,7 @@ libsystemd_journal_core_la_SOURCES = \
 	src/journal/journald-kmsg.h \
 	src/journal/journald-syslog.c \
 	src/journal/journald-syslog.h \
+	src/journal/journald-syslog-network.c \
 	src/journal/journald-stream.c \
 	src/journal/journald-stream.h \
 	src/journal/journald-server.c \
diff --git a/man/journald.conf.xml b/man/journald.conf.xml
index 364b58f..4fb037b 100644
--- a/man/journald.conf.xml
+++ b/man/journald.conf.xml
@@ -355,6 +355,18 @@
       </varlistentry>
 
       <varlistentry>
+        <term><varname>SysLogAddress=</varname></term>
+        <listitem><para>Controls whether log messages received by the
+        journal daemon shall be forwarded to a multicast UDP network
+        group in syslog RFC 5424 format.</para>
+
+        <para>The the address string format is similar to socket units. See
+        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><varname>TTYPath=</varname></term>
 
         <listitem><para>Change the console TTY to use if
diff --git a/src/journal/journald-gperf.gperf b/src/journal/journald-gperf.gperf
index 74554c1..9cdffbc 100644
--- a/src/journal/journald-gperf.gperf
+++ b/src/journal/journald-gperf.gperf
@@ -40,3 +40,4 @@ Journal.MaxLevelKMsg,       config_parse_log_level,  0, offsetof(Server, max_lev
 Journal.MaxLevelConsole,    config_parse_log_level,  0, offsetof(Server, max_level_console)
 Journal.MaxLevelWall,       config_parse_log_level,  0, offsetof(Server, max_level_wall)
 Journal.SplitMode,          config_parse_split_mode, 0, offsetof(Server, split_mode)
+Journal.SysLogAddress,      config_parse_syslog_network_address, 0, offsetof(Server, syslog_addr)
diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c
index 851625d..9fd370f 100644
--- a/src/journal/journald-native.c
+++ b/src/journal/journald-native.c
@@ -273,6 +273,9 @@ void server_process_native_message(
                 if (s->forward_to_syslog)
                         server_forward_syslog(s, priority, identifier, message, ucred, tv);
 
+                if (s->forward_to_network)
+                        server_forward_syslog_network(s, priority, identifier, message, ucred, tv);
+
                 if (s->forward_to_kmsg)
                         server_forward_kmsg(s, priority, identifier, message, ucred);
 
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index 7ee8174..de4ef50 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -86,7 +86,7 @@ static const char* const split_mode_table[_SPLIT_MAX] = {
 DEFINE_STRING_TABLE_LOOKUP(split_mode, SplitMode);
 DEFINE_CONFIG_PARSE_ENUM(config_parse_split_mode, split_mode, SplitMode, "Failed to parse split mode setting");
 
-static uint64_t available_space(Server *s, bool verbose) {
+uint64_t available_space(Server *s, bool verbose) {
         char ids[33];
         _cleanup_free_ char *p = NULL;
         sd_id128_t machine;
@@ -1356,6 +1356,35 @@ static int server_parse_config_file(Server *s) {
                                  false, s);
 }
 
+int config_parse_syslog_network_address(const char *unit,
+                                        const char *filename,
+                                        unsigned line,
+                                        const char *section,
+                                        unsigned section_line,
+                                        const char *lvalue,
+                                        int ltype,
+                                        const char *rvalue,
+                                        void *data,
+                                        void *userdata) {
+        Server *s = userdata;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = socket_address_parse(&s->syslog_addr, rvalue);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, -r,
+                           "Failed to parse address value, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        s->forward_to_network = true;
+        return 0;
+}
+
 static int server_dispatch_sync(sd_event_source *es, usec_t t, void *userdata) {
         Server *s = userdata;
 
@@ -1578,6 +1607,10 @@ int server_init(Server *s) {
         if (r < 0)
                 return r;
 
+        r = server_open_syslog_network_socket(s);
+        if (r < 0)
+                log_error_errno(r, "Failed to open syslog network socket. Ignoring: %m.");
+
         r = server_open_native_socket(s);
         if (r < 0)
                 return r;
@@ -1673,6 +1706,7 @@ void server_done(Server *s) {
         sd_event_unref(s->event);
 
         safe_close(s->syslog_fd);
+        safe_close(s->syslog_network_fd);
         safe_close(s->native_fd);
         safe_close(s->stdout_fd);
         safe_close(s->dev_kmsg_fd);
@@ -1682,6 +1716,9 @@ void server_done(Server *s) {
         if (s->rate_limit)
                 journal_rate_limit_free(s->rate_limit);
 
+        if (s->syslog_network_rate_limit)
+                journal_rate_limit_free(s->syslog_network_rate_limit);
+
         if (s->kernel_seqnum)
                 munmap(s->kernel_seqnum, sizeof(uint64_t));
 
@@ -1689,6 +1726,7 @@ void server_done(Server *s) {
         free(s->tty_path);
         free(s->cgroup_root);
         free(s->hostname_field);
+        free(s->hostname);
 
         if (s->mmap)
                 mmap_cache_unref(s->mmap);
diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h
index c96877c..ba9e456 100644
--- a/src/journal/journald-server.h
+++ b/src/journal/journald-server.h
@@ -27,6 +27,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 
+#include "socket-util.h"
 #include "sd-event.h"
 #include "journal-file.h"
 #include "hashmap.h"
@@ -56,6 +57,7 @@ typedef struct StdoutStream StdoutStream;
 
 typedef struct Server {
         int syslog_fd;
+        int syslog_network_fd;
         int native_fd;
         int stdout_fd;
         int dev_kmsg_fd;
@@ -86,6 +88,8 @@ typedef struct Server {
         size_t buffer_size;
 
         JournalRateLimit *rate_limit;
+        JournalRateLimit *syslog_network_rate_limit;
+
         usec_t sync_interval_usec;
         usec_t rate_limit_interval;
         unsigned rate_limit_burst;
@@ -98,12 +102,15 @@ typedef struct Server {
 
         bool forward_to_kmsg;
         bool forward_to_syslog;
+        bool forward_to_network;
         bool forward_to_console;
         bool forward_to_wall;
 
         unsigned n_forward_syslog_missed;
         usec_t last_warn_forward_syslog_missed;
 
+        unsigned n_forward_syslog_network_missed;
+
         uint64_t cached_available_space;
         usec_t cached_available_space_timestamp;
 
@@ -140,6 +147,9 @@ typedef struct Server {
         char machine_id_field[sizeof("_MACHINE_ID=") + 32];
         char boot_id_field[sizeof("_BOOT_ID=") + 32];
         char *hostname_field;
+        char *hostname;
+
+        SocketAddress syslog_addr;
 
         /* Cached cgroup root, so that we don't have to query that all the time */
         char *cgroup_root;
@@ -166,13 +176,17 @@ int config_parse_split_mode(const char *unit, const char *filename, unsigned lin
 const char *split_mode_to_string(SplitMode s) _const_;
 SplitMode split_mode_from_string(const char *s) _pure_;
 
+int config_parse_syslog_network_address(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue,void *data, void *userdata);
+
 void server_fix_perms(Server *s, JournalFile *f, uid_t uid);
 int server_init(Server *s);
 void server_done(Server *s);
 void server_sync(Server *s);
 void server_vacuum(Server *s);
 void server_rotate(Server *s);
+uint64_t available_space(Server *s, bool verbose);
 int server_schedule_sync(Server *s, int priority);
 int server_flush_to_var(Server *s);
 void server_maybe_append_tags(Server *s);
 int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata);
+int server_open_syslog_network_socket(Server *s);
diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c
index 942a857..ccc10a3 100644
--- a/src/journal/journald-stream.c
+++ b/src/journal/journald-stream.c
@@ -69,6 +69,7 @@ struct StdoutStream {
         int priority;
         bool level_prefix:1;
         bool forward_to_syslog:1;
+        bool forward_to_network:1;
         bool forward_to_kmsg:1;
         bool forward_to_console:1;
 
@@ -243,6 +244,9 @@ static int stdout_stream_log(StdoutStream *s, const char *p) {
         if (s->forward_to_syslog || s->server->forward_to_syslog)
                 server_forward_syslog(s->server, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL);
 
+        if (s->forward_to_network || s->server->forward_to_network)
+                server_forward_syslog_network(s->server, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL);
+
         if (s->forward_to_kmsg || s->server->forward_to_kmsg)
                 server_forward_kmsg(s->server, priority, s->identifier, p, &s->ucred);
 
diff --git a/src/journal/journald-syslog-network.c b/src/journal/journald-syslog-network.c
new file mode 100644
index 0000000..0f7b494
--- /dev/null
+++ b/src/journal/journald-syslog-network.c
@@ -0,0 +1,246 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2015 Susant Sahani
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stddef.h>
+#include <poll.h>
+
+#include "shared/in-addr-util.h"
+#include "journald-server.h"
+#include "journald-syslog.h"
+#include "systemd/sd-messages.h"
+
+/* Warn once every 30s if we missed syslog message */
+#define WARN_FORWARD_SYSLOG_MISSED_ID "syslog-network-missed"
+
+#define RFC_5424_NILVALUE "-"
+#define RFC_5424_PROTOCOL 1
+
+static void server_maybe_warn_forward_syslog_network_missed(Server *s, int priority) {
+        int r;
+
+        assert(s);
+
+        if (s->n_forward_syslog_network_missed <= 0)
+                return;
+
+        r = journal_rate_limit_test(s->syslog_network_rate_limit, WARN_FORWARD_SYSLOG_MISSED_ID,
+                                    priority & LOG_PRIMASK, available_space(s, false));
+        if (r == 0)
+                return;
+
+        server_driver_message(s, SD_MESSAGE_FORWARD_SYSLOG_MISSED,
+                              "Forwarding to syslog network missed %u messages.",
+                              s->n_forward_syslog_network_missed);
+
+        s->n_forward_syslog_network_missed = 0;
+}
+
+static int syslog_network_send(Server *s, struct iovec *iovec, unsigned n_iovec, int priority) {
+        struct msghdr mh = { };
+
+        assert(s);
+        assert(iovec);
+        assert(n_iovec > 0);
+
+        mh.msg_iov = iovec;
+        mh.msg_iovlen = n_iovec;
+
+        if (s->syslog_addr.sockaddr.sa.sa_family == AF_INET) {
+                mh.msg_name = &s->syslog_addr.sockaddr.sa;
+                mh.msg_namelen = sizeof(s->syslog_addr.sockaddr.sa);
+        } else if (s->syslog_addr.sockaddr.sa.sa_family == AF_INET6) {
+                mh.msg_name = &s->syslog_addr.sockaddr.in6;
+                mh.msg_namelen = sizeof(s->syslog_addr.sockaddr.in6);
+        } else
+                return -EAFNOSUPPORT;
+
+        if (sendmsg(s->syslog_network_fd, &mh, MSG_NOSIGNAL) >= 0)
+                return 0;
+
+        s->n_forward_syslog_network_missed++;
+
+        server_maybe_warn_forward_syslog_network_missed(s, priority);
+
+        return 0;
+}
+
+/* RFC3339 timestamp format: YYYY-MM-DDTHH:MM:SS[.frac]<+/->ZZ:ZZ */
+void format_rfc3339_timestamp(const struct timeval *tv, char *header_time, size_t header_size) {
+        char gm_buf[sizeof("+0530") + 1];
+        struct tm tm;
+        time_t t;
+
+        t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
+        localtime_r(&t, &tm);
+
+        strftime(header_time, header_size, "%Y-%m-%dT%T", &tm);
+
+        /* add fractional part */
+        if (tv)
+                snprintf(header_time + strlen(header_time), header_size, ".%06ld", tv->tv_usec);
+
+        /* format the timezone according to RFC */
+        xstrftime(gm_buf, "%z", &tm);
+        snprintf(header_time + strlen(header_time), header_size, "%.3s:%.2s ", gm_buf, gm_buf + 3);
+}
+
+/* The Syslog Protocol RFC5424 format :
+ * <PRI>VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP [SD-ID]s SP MSG
+ */
+void server_forward_syslog_network(Server *s,
+                                   int priority,
+                                   const char *identifier,
+                                   const char *message,
+                                   const struct ucred *ucred,
+                                   const struct timeval *tv) {
+        char header_pid[DECIMAL_STR_MAX(pid_t) + 1];
+        char header_priority[sizeof("< >1 ") + 1];
+        char header_time[FORMAT_TIMESTAMP_MAX];
+        struct iovec iov[13];
+        int n = 0;
+
+        assert(s);
+        assert(priority >= 0);
+        assert(priority <= 999);
+        assert(message);
+
+        if (LOG_PRI(priority) > s->max_level_syslog)
+                return;
+
+        /* First: priority field Second: Version  '<pri>version' */
+        snprintf(header_priority, sizeof(header_priority), "<%i>%i ", priority, RFC_5424_PROTOCOL);
+        IOVEC_SET_STRING(iov[n++], header_priority);
+
+        /* Third: timestamp */
+        format_rfc3339_timestamp(tv, header_time, sizeof(header_time));
+        IOVEC_SET_STRING(iov[n++], header_time);
+
+        /* Fourth: hostname */
+        if (s->hostname) {
+                IOVEC_SET_STRING(iov[n++], s->hostname);
+                IOVEC_SET_STRING(iov[n++], " ");
+        }
+
+        /* Fifth: app-name or tag */
+        if (identifier) {
+                IOVEC_SET_STRING(iov[n++], identifier);
+                IOVEC_SET_STRING(iov[n++], " ");
+        } else {
+                IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+                IOVEC_SET_STRING(iov[n++], " ");
+        }
+
+        /* Sixth: procid */
+        if (ucred) {
+                xsprintf(header_pid, PID_FMT , ucred->pid);
+
+                IOVEC_SET_STRING(iov[n++], header_pid);
+                IOVEC_SET_STRING(iov[n++], " ");
+        } else {
+                IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+                IOVEC_SET_STRING(iov[n++], " ");
+        }
+
+        /* Seventh: msgid */
+        IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+        IOVEC_SET_STRING(iov[n++], " ");
+
+        /* Eighth: [structured-data] */
+        IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+        IOVEC_SET_STRING(iov[n++], " ");
+
+        /* Ninth: message */
+        IOVEC_SET_STRING(iov[n++], message);
+
+        syslog_network_send(s, iov, n, priority);
+}
+
+static int syslog_network_fd(Server *s) {
+        const int ttl = 255;
+        const int one = 1;
+        int fd, r;
+
+        assert(s);
+
+        fd = socket(s->syslog_addr.sockaddr.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+        if (fd < 0)
+                return -errno;
+
+        r = setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+        if (r < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        r = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
+        if (r < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        r = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
+        if (r < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        s->syslog_network_fd = fd;
+
+        return fd;
+
+ fail:
+        fd = safe_close(fd);
+        return r;
+}
+
+int server_open_syslog_network_socket(Server *s) {
+        int r;
+
+        assert(s);
+
+        s->hostname = gethostname_malloc();
+        if (!s->hostname) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        if (s->syslog_addr.sockaddr.sa.sa_family == AF_INET || s->syslog_addr.sockaddr.sa.sa_family == AF_INET6) {
+
+                r = syslog_network_fd(s);
+                if (r < 0)
+                        goto fail;
+        } else {
+                r = -EAFNOSUPPORT;
+                goto fail;
+        }
+
+        s->syslog_network_rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst);
+        if (!s->syslog_network_rate_limit) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        return r;
+ fail:
+        s->forward_to_network = false;
+        return r;
+}
diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c
index 7d545ca..4ecbc43 100644
--- a/src/journal/journald-syslog.c
+++ b/src/journal/journald-syslog.c
@@ -336,6 +336,9 @@ void server_process_syslog_message(
         syslog_skip_date((char**) &buf);
         syslog_parse_identifier(&buf, &identifier, &pid);
 
+        if (s->forward_to_network)
+                server_forward_syslog_network(s, priority, identifier, buf, ucred, tv);
+
         if (s->forward_to_kmsg)
                 server_forward_kmsg(s, priority, identifier, buf, ucred);
 
diff --git a/src/journal/journald-syslog.h b/src/journal/journald-syslog.h
index 3774ebd..b239825 100644
--- a/src/journal/journald-syslog.h
+++ b/src/journal/journald-syslog.h
@@ -26,8 +26,10 @@
 int syslog_fixup_facility(int priority) _const_;
 
 size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid);
+void format_rfc3339_timestamp(const struct timeval *tv, char *header_time, size_t header_size);
 
 void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv);
+void server_forward_syslog_network(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv);
 
 void server_process_syslog_message(Server *s, const char *buf, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len);
 int server_open_syslog_socket(Server *s);
-- 
2.1.0



More information about the systemd-devel mailing list