journald: implement socket forwarding

This commit adds a new way of forwarding journal messages - forwarding
over a socket.

The socket can be any of AF_INET, AF_INET6, AF_UNIUX or AF_VSOCK.

The address to connect to is retrieved from the "journald.forward_address" credential.

It can also be specified in systemd-journald's unit file with ForwardAddress=
This commit is contained in:
Sam Leonard 2023-12-21 15:32:15 +00:00
parent 6813be2eba
commit f31cff849d
No known key found for this signature in database
GPG key ID: 96850F0978CE78F0
13 changed files with 473 additions and 48 deletions

View file

@ -288,7 +288,7 @@
large individual journal files may grow at most. This influences the granularity in which disk space
is made available through rotation, i.e. deletion of historic data. Defaults to one eighth of the
values configured with <varname>SystemMaxUse=</varname> and <varname>RuntimeMaxUse=</varname> capped
to 128M, so that usually seven rotated journal files are kept as history. If the journal compact
to 128M, so that usually seven rotated journal files are kept as history. If the journal compact
mode is enabled (enabled by default), the maximum file size is capped to 4G.</para>
<para>Specify values in bytes or use K, M, G, T, P, E as units for the specified sizes (equal to
@ -368,11 +368,13 @@
<term><varname>ForwardToKMsg=</varname></term>
<term><varname>ForwardToConsole=</varname></term>
<term><varname>ForwardToWall=</varname></term>
<term><varname>ForwardToSocket=</varname></term>
<listitem><para>Control whether log messages received by the journal daemon shall be forwarded to a
traditional syslog daemon, to the kernel log buffer (kmsg), to the system console, or sent as wall
messages to all logged-in users. These options take boolean arguments. If forwarding to syslog is
enabled but nothing reads messages from the socket, forwarding to syslog has no effect. By default,
traditional syslog daemon, to the kernel log buffer (kmsg), to the system console, sent as wall
messages to all logged-in users or sent over a socket. These options take boolean arguments except
for <literal>ForwardToSocket=</literal> which takes an an address instead. If forwarding
to syslog is enabled but nothing reads messages from the socket, forwarding to syslog has no effect. By default,
only forwarding to wall is enabled. These settings may be overridden at boot time with the kernel
command line options <literal>systemd.journald.forward_to_syslog</literal>,
<literal>systemd.journald.forward_to_kmsg</literal>,
@ -381,6 +383,16 @@
<literal>=</literal> and the following argument, true is assumed. Otherwise, the argument is parsed
as a boolean.</para>
<para>The socket forwarding address can be specified with the credential
<literal>journal.forward_to_socket</literal>. The following socket types are supported:</para>
<para><simplelist type="inline">
<member><constant>AF_INET</constant> (e.g. <literal>192.168.0.11:4444</literal>)</member>
<member><constant>AF_INET6</constant> (e.g. <literal>[2001:db8::ff00:42:8329]:4444</literal>)</member>
<member><constant>AF_UNIX</constant> (e.g. <literal>/run/host/journal/socket</literal>)</member>
<member><constant>AF_VSOCK</constant> (e.g. <literal>vsock:2:1234</literal>)</member>
</simplelist></para>
<para>When forwarding to the console, the TTY to log to can be changed with
<varname>TTYPath=</varname>, described below.</para>
@ -389,15 +401,27 @@
<command>systemd</command> will automatically disable kernel's rate-limiting applied to userspace
processes (equivalent to setting <literal>printk.devkmsg=on</literal>).</para>
<para>When forwarding over a socket the <ulink url="https://systemd.io/JOURNAL_EXPORT_FORMATS/#journal-export-format">
Journal Export Format</ulink> is used when sending over the wire. Notably this includes the metadata
field <varname>__REALTIME_TIMESTAMP</varname> so that
<command>systemd-journal-remote</command> (see
<citerefentry><refentrytitle>systemd-journal-remote.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
can be used to receive the forwarded journal entries.</para>
<para>Note: Forwarding is performed synchronously within journald, and may significantly affect its
performance. This is particularly relevant when using ForwardToConsole=yes in cloud environments,
where the console is often a slow, virtual serial port. Since journald is implemented as a
conventional single-process daemon, forwarding to a completely hung console will block journald.
This can have a cascading effect resulting in any services synchronously logging to the blocked
journal also becoming blocked. Unless actively debugging/developing something, it's generally
preferable to setup a <command>journalctl --follow</command> style service redirected to the
where the console is often a slow, virtual serial port.
Since journald is implemented as a conventional single-process daemon, forwarding to a completely
hung console will block journald. This can have a cascading effect resulting in any services synchronously
logging to the blocked journal also becoming blocked. Unless actively debugging/developing something, it's
generally preferable to setup a <command>journalctl --follow</command> style service redirected to the
console, instead of ForwardToConsole=yes, for production use.</para>
</listitem>
<para>Note: Using <varname>ForwardToSocket=</varname> over IPv4/IPv6 links can be very slow due to the synchronous nature of the sockets.
Take care to ensure your link is a low-latency local link if possible. Typically IP networking is not available everywhere
journald runs, e.g. in the initrd during boot. Consider using <constant>AF_VSOCK</constant>/<constant>AF_UNIX</constant> sockets for this if possible.
</para>
</varlistentry>
<varlistentry>
@ -406,11 +430,12 @@
<term><varname>MaxLevelKMsg=</varname></term>
<term><varname>MaxLevelConsole=</varname></term>
<term><varname>MaxLevelWall=</varname></term>
<term><varname>MaxLevelSocket=</varname></term>
<listitem><para>Controls the maximum log level of messages
that are stored in the journal, forwarded to syslog, kmsg, the
console or wall (if that is enabled, see above). As argument,
takes one of
console, a socket, or wall (if that is enabled, see above).
As argument, takes one of
<literal>emerg</literal>,
<literal>alert</literal>,
<literal>crit</literal>,
@ -422,9 +447,11 @@
or integer values in the range of 07 (corresponding to the
same levels). Messages equal or below the log level specified
are stored/forwarded, messages above are dropped. Defaults to
<literal>debug</literal> for <varname>MaxLevelStore=</varname>
and <varname>MaxLevelSyslog=</varname>, to ensure that the all
messages are stored in the journal and forwarded to syslog.
<literal>debug</literal> for <varname>MaxLevelStore=</varname>,
<varname>MaxLevelSyslog=</varname> and
<varname>MaxLevelSocket=</varname>, to ensure that the all
messages are stored in the journal, forwarded to syslog and
the socket if one exists.
Defaults to
<literal>notice</literal> for <varname>MaxLevelKMsg=</varname>,
<literal>info</literal> for <varname>MaxLevelConsole=</varname>,
@ -435,7 +462,8 @@
<literal>systemd.journald.max_level_syslog=</literal>,
<literal>systemd.journald.max_level_kmsg=</literal>,
<literal>systemd.journald.max_level_console=</literal>,
<literal>systemd.journald.max_level_wall=</literal>.</para>
<literal>systemd.journald.max_level_wall=</literal>,
<literal>systemd.journald.max_level_socket=</literal>.</para>
<xi:include href="version-info.xml" xpointer="v185"/>
</listitem>

View file

@ -296,6 +296,18 @@
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>journal.forward_to_socket</varname></term>
<listitem>
<para>Used by
<citerefentry><refentrytitle>systemd-journald</refentrytitle><manvolnum>8</manvolnum></citerefentry>
to determine where to forward log messages for socket forwarding, see
<citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>vmm.notify_socket</varname></term>
<listitem>

View file

@ -19,35 +19,37 @@ struct ConfigPerfItem;
%struct-type
%includes
%%
Journal.Storage, config_parse_storage, 0, offsetof(Server, storage)
Journal.Compress, config_parse_compress, 0, offsetof(Server, compress)
Journal.Seal, config_parse_bool, 0, offsetof(Server, seal)
Journal.ReadKMsg, config_parse_bool, 0, offsetof(Server, read_kmsg)
Journal.Audit, config_parse_tristate, 0, offsetof(Server, set_audit)
Journal.SyncIntervalSec, config_parse_sec, 0, offsetof(Server, sync_interval_usec)
Journal.Storage, config_parse_storage, 0, offsetof(Server, storage)
Journal.Compress, config_parse_compress, 0, offsetof(Server, compress)
Journal.Seal, config_parse_bool, 0, offsetof(Server, seal)
Journal.ReadKMsg, config_parse_bool, 0, offsetof(Server, read_kmsg)
Journal.Audit, config_parse_tristate, 0, offsetof(Server, set_audit)
Journal.SyncIntervalSec, config_parse_sec, 0, offsetof(Server, sync_interval_usec)
# The following is a legacy name for compatibility
Journal.RateLimitInterval, config_parse_sec, 0, offsetof(Server, ratelimit_interval)
Journal.RateLimitIntervalSec,config_parse_sec, 0, offsetof(Server, ratelimit_interval)
Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, ratelimit_burst)
Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_use)
Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_size)
Journal.SystemKeepFree, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.keep_free)
Journal.SystemMaxFiles, config_parse_uint64, 0, offsetof(Server, system_storage.metrics.n_max_files)
Journal.RuntimeMaxUse, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_use)
Journal.RuntimeMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_size)
Journal.RuntimeKeepFree, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.keep_free)
Journal.RuntimeMaxFiles, config_parse_uint64, 0, offsetof(Server, runtime_storage.metrics.n_max_files)
Journal.MaxRetentionSec, config_parse_sec, 0, offsetof(Server, max_retention_usec)
Journal.MaxFileSec, config_parse_sec, 0, offsetof(Server, max_file_usec)
Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog)
Journal.ForwardToKMsg, config_parse_bool, 0, offsetof(Server, forward_to_kmsg)
Journal.ForwardToConsole, config_parse_bool, 0, offsetof(Server, forward_to_console)
Journal.ForwardToWall, config_parse_bool, 0, offsetof(Server, forward_to_wall)
Journal.TTYPath, config_parse_path, 0, offsetof(Server, tty_path)
Journal.MaxLevelStore, config_parse_log_level, 0, offsetof(Server, max_level_store)
Journal.MaxLevelSyslog, config_parse_log_level, 0, offsetof(Server, max_level_syslog)
Journal.MaxLevelKMsg, config_parse_log_level, 0, offsetof(Server, max_level_kmsg)
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.LineMax, config_parse_line_max, 0, offsetof(Server, line_max)
Journal.RateLimitInterval, config_parse_sec, 0, offsetof(Server, ratelimit_interval)
Journal.RateLimitIntervalSec,config_parse_sec, 0, offsetof(Server, ratelimit_interval)
Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, ratelimit_burst)
Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_use)
Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_size)
Journal.SystemKeepFree, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.keep_free)
Journal.SystemMaxFiles, config_parse_uint64, 0, offsetof(Server, system_storage.metrics.n_max_files)
Journal.RuntimeMaxUse, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_use)
Journal.RuntimeMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_size)
Journal.RuntimeKeepFree, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.keep_free)
Journal.RuntimeMaxFiles, config_parse_uint64, 0, offsetof(Server, runtime_storage.metrics.n_max_files)
Journal.MaxRetentionSec, config_parse_sec, 0, offsetof(Server, max_retention_usec)
Journal.MaxFileSec, config_parse_sec, 0, offsetof(Server, max_file_usec)
Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog)
Journal.ForwardToKMsg, config_parse_bool, 0, offsetof(Server, forward_to_kmsg)
Journal.ForwardToConsole, config_parse_bool, 0, offsetof(Server, forward_to_console)
Journal.ForwardToWall, config_parse_bool, 0, offsetof(Server, forward_to_wall)
Journal.ForwardToSocket, config_parse_forward_to_socket, 0, offsetof(Server, forward_to_socket)
Journal.TTYPath, config_parse_path, 0, offsetof(Server, tty_path)
Journal.MaxLevelStore, config_parse_log_level, 0, offsetof(Server, max_level_store)
Journal.MaxLevelSyslog, config_parse_log_level, 0, offsetof(Server, max_level_syslog)
Journal.MaxLevelKMsg, config_parse_log_level, 0, offsetof(Server, max_level_kmsg)
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.MaxLevelSocket, config_parse_log_level, 0, offsetof(Server, max_level_socket)
Journal.SplitMode, config_parse_split_mode, 0, offsetof(Server, split_mode)
Journal.LineMax, config_parse_line_max, 0, offsetof(Server, line_max)

View file

@ -18,6 +18,7 @@
#include "audit-util.h"
#include "cgroup-util.h"
#include "conf-parser.h"
#include "creds-util.h"
#include "dirent-util.h"
#include "extract-word.h"
#include "fd-util.h"
@ -39,6 +40,7 @@
#include "journald-native.h"
#include "journald-rate-limit.h"
#include "journald-server.h"
#include "journald-socket.h"
#include "journald-stream.h"
#include "journald-syslog.h"
#include "log.h"
@ -51,6 +53,7 @@
#include "rm-rf.h"
#include "selinux-util.h"
#include "signal-util.h"
#include "socket-netlink.h"
#include "socket-util.h"
#include "stdio-util.h"
#include "string-table.h"
@ -1151,6 +1154,8 @@ static void server_dispatch_message_real(
else
journal_uid = 0;
server_forward_socket(s, iovec, n, priority);
server_write_to_journal(s, journal_uid, iovec, n, priority);
}
@ -1838,6 +1843,17 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
else
s->max_level_wall = r;
} else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_socket")) {
if (proc_cmdline_value_missing(key, value))
return 0;
r = log_level_from_string(value);
if (r < 0)
log_warning("Failed to parse max level socket value \"%s\". Ignoring.", value);
else
s->max_level_socket = r;
} else if (startswith(key, "systemd.journald"))
log_warning("Unknown journald kernel command line option \"%s\". Ignoring.", key);
@ -2443,6 +2459,29 @@ static int server_setup_memory_pressure(Server *s) {
return 0;
}
static void server_load_credentials(Server *s) {
_cleanup_free_ void *data = NULL;
int r;
assert(s);
/* if we already have a forward address from config don't load the credential */
if (s->forward_to_socket.sockaddr.sa.sa_family != AF_UNSPEC) {
log_debug("Socket forward address already set not loading journal.forward_to_socket");
return;
}
r = read_credential("journal.forward_to_socket", &data, NULL);
if (r < 0) {
log_debug_errno(r, "Failed to read credential journal.forward_to_socket, ignoring: %m");
return;
}
r = socket_address_parse(&s->forward_to_socket, data);
if (r < 0)
log_debug_errno(r, "Failed to parse credential journal.forward_to_socket, ignoring: %m");
}
int server_new(Server **ret) {
_cleanup_(server_freep) Server *s = NULL;
@ -2460,6 +2499,7 @@ int server_new(Server **ret) {
.audit_fd = -EBADF,
.hostname_fd = -EBADF,
.notify_fd = -EBADF,
.forward_socket_fd = -EBADF,
.compress.enabled = true,
.compress.threshold_bytes = UINT64_MAX,
@ -2476,6 +2516,7 @@ int server_new(Server **ret) {
.ratelimit_burst = DEFAULT_RATE_LIMIT_BURST,
.forward_to_wall = true,
.forward_to_socket = { .sockaddr.sa.sa_family = AF_UNSPEC },
.max_file_usec = DEFAULT_MAX_FILE_USEC,
@ -2484,6 +2525,7 @@ int server_new(Server **ret) {
.max_level_kmsg = LOG_NOTICE,
.max_level_console = LOG_INFO,
.max_level_wall = LOG_EMERG,
.max_level_socket = LOG_DEBUG,
.line_max = DEFAULT_LINE_MAX,
@ -2524,6 +2566,8 @@ int server_init(Server *s, const char *namespace) {
server_parse_config_file(s);
server_load_credentials(s);
if (!s->namespace) {
/* Parse kernel command line, but only if we are not a namespace instance */
r = proc_cmdline_parse(parse_proc_cmdline_item, s, PROC_CMDLINE_STRIP_RD_PREFIX);
@ -2796,6 +2840,7 @@ Server* server_free(Server *s) {
safe_close(s->audit_fd);
safe_close(s->hostname_fd);
safe_close(s->notify_fd);
safe_close(s->forward_socket_fd);
if (s->ratelimit)
journal_ratelimit_free(s->ratelimit);
@ -2931,3 +2976,34 @@ int config_parse_compress(
return 0;
}
int config_parse_forward_to_socket(
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) {
SocketAddress* addr = ASSERT_PTR(data);
int r;
assert(unit);
assert(filename);
assert(rvalue);
if (isempty(rvalue))
*addr = (SocketAddress) { .sockaddr.sa.sa_family = AF_UNSPEC };
else {
r = socket_address_parse(addr, rvalue);
if (r < 0)
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse ForwardToSocket= value, ignoring: %s", rvalue);
}
return 0;
}

View file

@ -5,6 +5,7 @@
#include <sys/types.h>
#include "sd-event.h"
#include "socket-util.h"
typedef struct Server Server;
@ -78,6 +79,7 @@ struct Server {
int audit_fd;
int hostname_fd;
int notify_fd;
int forward_socket_fd;
sd_event *event;
@ -123,6 +125,7 @@ struct Server {
bool forward_to_syslog;
bool forward_to_console;
bool forward_to_wall;
SocketAddress forward_to_socket;
unsigned n_forward_syslog_missed;
usec_t last_warn_forward_syslog_missed;
@ -142,6 +145,7 @@ struct Server {
int max_level_kmsg;
int max_level_console;
int max_level_wall;
int max_level_socket;
Storage storage;
SplitMode split_mode;
@ -214,6 +218,7 @@ const struct ConfigPerfItem* journald_gperf_lookup(const char *key, GPERF_LEN_TY
CONFIG_PARSER_PROTOTYPE(config_parse_storage);
CONFIG_PARSER_PROTOTYPE(config_parse_line_max);
CONFIG_PARSER_PROTOTYPE(config_parse_compress);
CONFIG_PARSER_PROTOTYPE(config_parse_forward_to_socket);
const char *storage_to_string(Storage s) _const_;
Storage storage_from_string(const char *s) _pure_;

View file

@ -0,0 +1,162 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <string.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include "fd-util.h"
#include "iovec-util.h"
#include "journald-socket.h"
#include "log.h"
#include "macro.h"
#include "process-util.h"
#include "socket-util.h"
#include "sparse-endian.h"
void server_open_forward_socket(Server *s) {
_cleanup_close_ int socket_fd = -EBADF;
const SocketAddress *addr;
int family;
assert(s);
/* nop if there is nothing to do */
if (s->forward_to_socket.sockaddr.sa.sa_family == AF_UNSPEC || s->namespace || s->forward_socket_fd >= 0)
return;
addr = &s->forward_to_socket;
family = socket_address_family(addr);
if (!IN_SET(family, AF_UNIX, AF_INET, AF_INET6, AF_VSOCK)) {
log_debug("Unsupported socket type for forward socket: %d", family);
return;
}
socket_fd = socket(family, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (socket_fd < 0) {
log_debug_errno(errno, "Failed to create forward socket, ignoring: %m");
return;
}
if (connect(socket_fd, &addr->sockaddr.sa, addr->size) < 0) {
log_debug_errno(errno, "Failed to connect to remote address for forwarding, ignoring: %m");
return;
}
s->forward_socket_fd = TAKE_FD(socket_fd);
log_debug("Successfully connected to remote address for forwarding");
}
static inline bool must_serialise(struct iovec iov) {
/* checks an iovec of the form FIELD=VALUE to see if VALUE needs binary safe serialisation:
* See https://systemd.io/JOURNAL_EXPORT_FORMATS/#journal-export-format for more information
* on binary safe serialisation for the journal export format */
assert(iov.iov_len == 0 || iov.iov_base);
const uint8_t *s = iov.iov_base;
bool before_value = true;
FOREACH_ARRAY(c, s, iov.iov_len) {
if (before_value)
before_value = *c != (uint8_t)'=';
else if (*c < (uint8_t)' ' && *c != (uint8_t)'\t')
return true;
}
return false;
}
void server_forward_socket(
Server *s,
const struct iovec *iovec,
size_t n_iovec,
int priority) {
_cleanup_free_ struct iovec *iov_alloc = NULL;
struct iovec *iov = NULL;
_cleanup_free_ le64_t *len_alloc = NULL;
le64_t *len = NULL;
assert(s);
assert(iovec);
assert(n_iovec > 0);
if (LOG_PRI(priority) > s->max_level_socket)
return;
server_open_forward_socket(s);
/* if we failed to open a socket just return */
if (s->forward_socket_fd < 0)
return;
/* we need a newline after each iovec + 4 for each we have to serialise in a binary safe way
* +1 for the final __REALTIME_TIMESTAMP metadata field */
size_t n = n_iovec * 5 + 1;
if (n < ALLOCA_MAX / (sizeof(struct iovec) + sizeof(le64_t)) / 2) {
iov = newa(struct iovec, n);
len = newa(le64_t, n_iovec);
} else {
iov_alloc = new(struct iovec, n);
if (!iov_alloc) {
log_oom();
return;
}
iov = iov_alloc;
len_alloc = new(le64_t, n_iovec);
if (!len_alloc) {
log_oom();
return;
}
len = len_alloc;
}
struct iovec nl = IOVEC_MAKE_STRING("\n");
size_t iov_idx = 0, len_idx = 0;
FOREACH_ARRAY(i, iovec, n_iovec) {
if (must_serialise(*i)) {
const uint8_t *c;
c = memchr(i->iov_base, '=', i->iov_len);
/* this should never happen */
if (_unlikely_(!c || c == i->iov_base)) {
log_error("Found invalid journal field, refusing to forward.");
return;
}
/* write the field name */
iov[iov_idx++] = IOVEC_MAKE(i->iov_base, c - (uint8_t*) i->iov_base);
iov[iov_idx++] = nl;
/* write the length of the value */
len[len_idx] = htole64(i->iov_len - (c - (uint8_t*) i->iov_base) - 1);
iov[iov_idx++] = IOVEC_MAKE(&len[len_idx++], sizeof(le64_t));
/* write the raw binary value */
iov[iov_idx++] = IOVEC_MAKE(c + 1, i->iov_len - (c - (uint8_t*) i->iov_base) - 1);
} else
/* if it doesn't need special treatment just write the value out */
iov[iov_idx++] = *i;
iov[iov_idx++] = nl;
}
/* synthesise __REALTIME_TIMESTAMP as the last argument so systemd-journal-upload can receive these export messages */
char buf[sizeof("__REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t) + 2];
xsprintf(buf, "__REALTIME_TIMESTAMP="USEC_FMT"\n\n", now(CLOCK_REALTIME));
iov[iov_idx++] = IOVEC_MAKE_STRING(buf);
if (writev(s->forward_socket_fd, iov, iov_idx) < 0) {
log_debug_errno(errno, "Failed to forward log message over socket: %m");
/* if we failed to send once we will probably fail again so wait for a new connection to
* establish before attempting to forward again */
s->forward_socket_fd = safe_close(s->forward_socket_fd);
}
}

View file

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "journald-server.h"
#include "socket-util.h"
void server_forward_socket(Server *s, const struct iovec *iovec, size_t n, int priority);
void server_open_forward_socket(Server *s);

View file

@ -44,6 +44,7 @@
#MaxLevelKMsg=notice
#MaxLevelConsole=info
#MaxLevelWall=emerg
#MaxLevelSocket=debug
#LineMax=48K
#ReadKMsg=yes
#Audit=yes

View file

@ -12,6 +12,7 @@ sources = files(
'journald-stream.c',
'journald-syslog.c',
'journald-wall.c',
'journald-socket.c',
)
sources += custom_target(

View file

@ -1,8 +1,15 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <netinet/in.h>
#include <stdbool.h>
#include <string.h>
#include <sys/un.h>
#include "journald-server.h"
#include "log.h"
#include "path-util.h"
#include "socket-util.h"
#include "sparse-endian.h"
#include "tests.h"
#define _COMPRESS_PARSE_CHECK(str, enab, thresh, varname) \
@ -47,4 +54,125 @@ TEST(config_compress) {
COMPRESS_PARSE_CHECK("", true, UINT64_MAX);
}
#define _FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(str, addr, varname) \
do { \
SocketAddress varname = {}; \
config_parse_forward_to_socket("", "", 0, "", 0, "", 0, str, \
&varname, NULL); \
assert_se(socket_address_verify(&varname, true) < 0); \
} while (0)
#define FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(str) \
_FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(str, addr, conf##__COUNTER__)
#define _FORWARD_TO_SOCKET_PARSE_CHECK(str, addr, varname) \
do { \
SocketAddress varname = {}; \
config_parse_forward_to_socket("", "", 0, "", 0, "", 0, str, \
&varname, NULL); \
buf = mfree(buf); \
buf2 = mfree(buf2); \
socket_address_print(&varname, &buf);\
socket_address_print(&addr, &buf2);\
log_info("\"%s\" parsed as \"%s\", should be \"%s\"", str, buf, buf2); \
log_info("socket_address_verify(&addr, false) = %d", socket_address_verify(&addr, false)); \
log_info("socket_address_verify(&varname, false) = %d", socket_address_verify(&varname, false)); \
log_info("socket_address_family(&addr) = %d", socket_address_family(&addr)); \
log_info("socket_address_family(&varname) = %d", socket_address_family(&varname)); \
log_info("addr.size = %u", addr.size); \
log_info("varname.size = %u", varname.size); \
assert_se(socket_address_equal(&varname, &addr)); \
} while (0)
#define FORWARD_TO_SOCKET_PARSE_CHECK(str, addr) \
_FORWARD_TO_SOCKET_PARSE_CHECK(str, addr, conf##__COUNTER__)
TEST(config_forward_to_socket) {
SocketAddress addr;
_cleanup_free_ char *buf = NULL, *buf2 = NULL;
/* Valid AF_UNIX */
addr = (SocketAddress) {
.sockaddr.un = (struct sockaddr_un) {
.sun_family = AF_UNIX,
.sun_path = "/run/host/journal/socket",
},
.size = offsetof(struct sockaddr_un, sun_path) + strlen("/run/host/journal/socket") + 1,
};
FORWARD_TO_SOCKET_PARSE_CHECK("/run/host/journal/socket", addr);
addr.size -= 1;
memcpy(addr.sockaddr.un.sun_path, "\0run/host/journal/socket", sizeof("\0run/host/journal/socket"));
FORWARD_TO_SOCKET_PARSE_CHECK("@run/host/journal/socket", addr);
/* Valid AF_INET */
addr = (SocketAddress) {
.sockaddr.in = (struct sockaddr_in) {
.sin_family = AF_INET,
.sin_addr = { htobe32(0xC0A80001) },
.sin_port = htobe16(1234),
},
.size = sizeof(struct sockaddr_in),
};
FORWARD_TO_SOCKET_PARSE_CHECK("192.168.0.1:1234", addr);
/* Valid AF_INET6 */
addr = (SocketAddress) {
.sockaddr.in6 = (struct sockaddr_in6) {
.sin6_family = AF_INET6,
.sin6_addr = (struct in6_addr) {
.s6_addr16 = {
htobe16(0x2001),
htobe16(0xdb8),
htobe16(0x4006),
htobe16(0x812),
0, 0, 0,
htobe16(0x200e)
}
},
.sin6_port = htobe16(8080),
},
.size = sizeof(struct sockaddr_in6),
};
FORWARD_TO_SOCKET_PARSE_CHECK("[2001:db8:4006:812::200e]:8080", addr);
/* Valid AF_VSOCK */
addr = (SocketAddress) {
.sockaddr.vm = (struct sockaddr_vm) {
.svm_family = AF_VSOCK,
.svm_cid = 123456,
.svm_port = 654321,
},
.size = sizeof(struct sockaddr_vm),
};
FORWARD_TO_SOCKET_PARSE_CHECK("vsock:123456:654321", addr);
/* Invalid IPv4 */
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("256.123.45.12:1235");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("252.123.45.12:123500");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("252.123.45.12:0");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("252.123.45.12:-1");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("-1.123.45.12:22");
/* Invalid IPv6 */
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[2001:db8:4006:812::200e]:80800");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[1ffff:db8:4006:812::200e]:8080");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[-1:db8:4006:812::200e]:8080");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[2001:db8:4006:812::200e]:-1");
/* Invalid UNIX */
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("a/b/c");
/* Invalid VSock */
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:4294967296:1234");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:1234:4294967296");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:abcd:1234");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:1234:abcd");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:1234");
/* Invalid Case */
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("");
FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("ahh yes sockets, mmh");
}
DEFINE_TEST_MAIN(LOG_INFO);

View file

@ -865,6 +865,7 @@ MaxLevelKMsg=
MaxLevelStore=
MaxLevelSyslog=
MaxLevelWall=
MaxLevelSocket=
MaxRetentionSec=
MaxUse=
MemoryDenyWriteExecute=

View file

@ -343,7 +343,7 @@ done
# Aux verbs & assorted checks
systemctl is-active "*-journald.service"
systemctl cat "*journal*"
systemctl cat "*udevd*"
systemctl cat "$UNIT_NAME"
systemctl help "$UNIT_NAME"
systemctl service-watchdogs

View file

@ -29,6 +29,7 @@ IgnoreOnIsolate=yes
DeviceAllow=char-* rw
ExecStart={{LIBEXECDIR}}/systemd-journald
FileDescriptorStoreMax=4224
ImportCredential=journal.*
IPAddressDeny=any
LockPersonality=yes
MemoryDenyWriteExecute=yes
@ -37,7 +38,7 @@ OOMScoreAdjust=-250
ProtectClock=yes
Restart=always
RestartSec=0
RestrictAddressFamilies=AF_UNIX AF_NETLINK
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_VSOCK AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes