Merge pull request #31218 from CodethinkLabs/vmspawn/journal_forwarding

vmspawn: support journal forwarding
This commit is contained in:
Luca Boccassi 2024-02-16 12:09:52 +00:00 committed by GitHub
commit 92d1419eb8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 591 additions and 69 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

@ -312,6 +312,25 @@
</variablelist>
</refsect2>
<refsect2>
<title>Integration Options</title>
<variablelist>
<varlistentry>
<term><option>--forward-journal=</option><replaceable>FILE|DIR</replaceable></term>
<listitem><para>Forward the virtual machine's journal entries to the host.</para>
<para><citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>
Is currently used to receive the guest VM's forwarded journal entries. For more information on the semantics
of supplying a file vs a directory here see <option>-o</option>/<option>--output</option> in
<citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
</variablelist>
</refsect2>
<refsect2>
<title>Credentials</title>

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

@ -516,7 +516,9 @@ static int accept_connection(
switch (socket_address_family(addr)) {
case AF_INET:
case AF_INET6: {
case AF_INET6:
case AF_VSOCK:
case AF_UNIX: {
_cleanup_free_ char *a = NULL;
char *b;

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"
@ -52,6 +54,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"
@ -1152,6 +1155,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);
}
@ -1860,6 +1865,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);
@ -2465,6 +2481,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;
@ -2482,6 +2521,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,
@ -2498,6 +2538,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,
@ -2506,6 +2547,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,
@ -2546,6 +2588,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);
@ -2818,6 +2862,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);
@ -2920,9 +2965,13 @@ int config_parse_compress(
void *data,
void *userdata) {
JournalCompressOptions* compress = data;
JournalCompressOptions* compress = ASSERT_PTR(data);
int r;
assert(unit);
assert(filename);
assert(rvalue);
if (isempty(rvalue)) {
compress->enabled = true;
compress->threshold_bytes = UINT64_MAX;
@ -2949,3 +2998,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

@ -82,6 +82,7 @@ static RuntimeMountContext arg_runtime_mounts = {};
static SettingsMask arg_settings_mask = 0;
static char *arg_firmware = NULL;
static char *arg_runtime_directory = NULL;
static char *arg_forward_journal = NULL;
static bool arg_runtime_directory_created = false;
static bool arg_privileged = false;
static char **arg_kernel_cmdline_extra = NULL;
@ -96,6 +97,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep);
STATIC_DESTRUCTOR_REGISTER(arg_linux, freep);
STATIC_DESTRUCTOR_REGISTER(arg_initrds, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done);
STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep);
STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep);
static int help(void) {
@ -145,6 +147,10 @@ static int help(void) {
" the VM.\n"
" --bind-ro=SOURCE[:TARGET]\n"
" Similar, but creates a read-only mount\n"
"\n%3$sIntegration:%4$s\n"
" --forward-journal=FILE|DIR\n"
" Forward the virtual machine's journal entries to\n"
" the host.\n"
"\n%3$sCredentials:%4$s\n"
" --set-credential=ID:VALUE\n"
" Pass a credential with literal value to the\n"
@ -181,6 +187,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_BIND_RO,
ARG_SECURE_BOOT,
ARG_PRIVATE_USERS,
ARG_FORWARD_JOURNAL,
ARG_SET_CREDENTIAL,
ARG_LOAD_CREDENTIAL,
ARG_FIRMWARE,
@ -209,6 +216,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "bind-ro", required_argument, NULL, ARG_BIND_RO },
{ "secure-boot", required_argument, NULL, ARG_SECURE_BOOT },
{ "private-users", required_argument, NULL, ARG_PRIVATE_USERS },
{ "forward-journal", required_argument, NULL, ARG_FORWARD_JOURNAL },
{ "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL },
{ "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
{ "firmware", required_argument, NULL, ARG_FIRMWARE },
@ -365,6 +373,12 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_FORWARD_JOURNAL:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_forward_journal);
if (r < 0)
return r;
break;
case ARG_SET_CREDENTIAL: {
r = machine_credential_set(&arg_credentials, optarg);
if (r < 0)
@ -664,6 +678,45 @@ static int start_tpm(sd_bus *bus, const char *scope, const char *tpm, const char
return 0;
}
static int start_systemd_journal_remote(sd_bus *bus, const char *scope, unsigned port, const char *sd_journal_remote, char **listen_address) {
_cleanup_free_ char *scope_prefix = NULL;
_cleanup_(socket_service_pair_done) SocketServicePair ssp = {
.socket_type = SOCK_STREAM,
};
int r;
assert(bus);
assert(scope);
assert(sd_journal_remote);
r = unit_name_to_prefix(scope, &scope_prefix);
if (r < 0)
return log_error_errno(r, "Failed to strip .scope suffix from scope: %m");
ssp.unit_name_prefix = strjoin(scope_prefix, "-forward-journal");
if (!ssp.unit_name_prefix)
return log_oom();
r = asprintf(&ssp.listen_address, "vsock:2:%u", port);
if (r < 0)
return log_oom();
ssp.exec_start = strv_new(sd_journal_remote,
"--output", arg_forward_journal,
"--split-mode", endswith(arg_forward_journal, ".journal") ? "none" : "host");
if (!ssp.exec_start)
return log_oom();
r = start_socket_service_pair(bus, scope, &ssp);
if (r < 0)
return r;
if (listen_address)
*listen_address = TAKE_PTR(ssp.listen_address);
return 0;
}
static int discover_root(char **ret) {
int r;
_cleanup_(dissected_image_unrefp) DissectedImage *image = NULL;
@ -1105,9 +1158,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
return log_oom();
_cleanup_close_ int child_vsock_fd = -EBADF;
unsigned child_cid = arg_vsock_cid;
if (use_vsock) {
int device_fd = vhost_device_fd;
unsigned child_cid = arg_vsock_cid;
if (device_fd < 0) {
child_vsock_fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC);
@ -1155,24 +1208,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (r < 0)
return log_oom();
if (ARCHITECTURE_SUPPORTS_SMBIOS)
FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
_cleanup_free_ char *cred_data_b64 = NULL;
ssize_t n;
n = base64mem(cred->data, cred->size, &cred_data_b64);
if (n < 0)
return log_oom();
r = strv_extend(&cmdline, "-smbios");
if (r < 0)
return log_oom();
r = strv_extendf(&cmdline, "type=11,value=io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64);
if (r < 0)
return log_oom();
}
r = strv_extend(&cmdline, "-drive");
if (r < 0)
return log_oom();
@ -1411,6 +1446,43 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
return log_oom();
}
if (arg_forward_journal) {
_cleanup_free_ char *sd_journal_remote = NULL, *listen_address = NULL, *cred = NULL;
r = find_executable("systemd-journal-remote", &sd_journal_remote);
if (r < 0)
return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m");
r = start_systemd_journal_remote(bus, trans_scope, child_cid, sd_journal_remote, &listen_address);
if (r < 0)
return r;
cred = strjoin("journal.forward_to_socket:", listen_address);
if (!cred)
return log_oom();
r = machine_credential_set(&arg_credentials, cred);
if (r < 0)
return r;
}
if (ARCHITECTURE_SUPPORTS_SMBIOS)
FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
_cleanup_free_ char *cred_data_b64 = NULL;
ssize_t n;
n = base64mem(cred->data, cred->size, &cred_data_b64);
if (n < 0)
return log_oom();
r = strv_extend(&cmdline, "-smbios");
if (r < 0)
return log_oom();
r = strv_extendf(&cmdline, "type=11,value=io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64);
if (r < 0)
return log_oom();
}
if (use_vsock) {
notify_sock_fd = open_vsock();
if (notify_sock_fd < 0)

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