1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-26 08:55:40 +03:00

Compare commits

...

25 Commits

Author SHA1 Message Date
Nick Rosbrook
8a4a18f5a0
Merge 0eacbc1240 into f7078de515 2024-10-26 02:03:31 +08:00
Yu Watanabe
f7078de515
Merge pull request #34884 from poettering/run0-disconnect-fix
run: reconnect if our dbus connection is terminated
2024-10-26 02:50:48 +09:00
Yu Watanabe
6d6048b4cb
Merge pull request #34881 from poettering/run0-ui-tweaks
run0: various UI tweaks
2024-10-26 02:49:48 +09:00
Ivan Kruglov
10a48938ef machine: operation should not send a response when 'done' callback set 2024-10-26 02:45:53 +09:00
Lennart Poettering
b58b13f1c6 test: add brief testcase for systemd-run disconnect handling 2024-10-25 17:51:04 +02:00
Lennart Poettering
c8f59296bf run: reconnect if our dbus connection is terminated
We must be prepared that systemd temporarily drops off the bus or
disconnects our direct connections (due to systemctl daemon-reexec or
so). Hence automatically reconnect when we watch the unit status, and
handle this case gracefully.

Fixes: #32906 #27204
2024-10-25 17:51:04 +02:00
Lennart Poettering
d585085f57 update TODO 2024-10-25 17:32:19 +02:00
Lennart Poettering
ff4b6a1915 run: drop "-" prefix from command line when generating unit description
Let's not confuse users with the login shell indicator and drop it from
the description. This means a run0 session will now usually show up with
a description of "[run0] /bin/bash" rather than "[run0] -/bin/bash".
2024-10-25 17:32:19 +02:00
Lennart Poettering
d9f68f48f7 run: prefix unit description with our own process name
I think we should try to communicate clearly if something is a run0
session, or a systemd-run invocation. Hence, let's initialize the
description so that the command is prefixed by
program_invocation_short_name.

Effectively this means that our run0 sessions now appear as services
with a description of "[run0] -/bin/bash"
2024-10-25 17:32:19 +02:00
Lennart Poettering
0310b2a60b run: tweak how we name our transient units
The current logic is a bit complex how systemd-run units are called. It
used to be just the unique ID of the dbus connection. Which was nice,
since its system-widely, uniquely assigned to us. But this didn't work
out well, due to direct connections to PID 1 and due to soft reboots.

We nowadays have a better ID to use though, with nicer properties: the
kernel manages a pidfd ID for every process after all, and it's globally
unique, for any process, and regardless of soft reboots. Hence use that
for naming preferably, and just keep one branch with a randomized name
as fallback.
2024-10-25 17:32:19 +02:00
Lennart Poettering
115fac3c29 run0: optionally show superhero emoji on each shell prompt
This makes use of the infra introduced in 229d4a9806 to indicate visually on each prompt that we are in superuser mode temporarily.
pick ad5de3222f userdbctl: add some basic client-side filtering
2024-10-25 17:31:06 +02:00
Lennart Poettering
9d8f5e22f8
Merge pull request #34891 from poettering/run0-pty
run0: add --pty and --pipe switches to force allocation of a pty or pipe
2024-10-25 16:25:01 +02:00
Lennart Poettering
6fb0c52295 ci: add some basic testing of the new --pty and --pipe switches 2024-10-25 14:14:26 +02:00
Lennart Poettering
edd10ab29c run0: add options to force allocation of PTY or of pipe use
Fixes: #33033
2024-10-25 14:14:26 +02:00
Lennart Poettering
988053eac3 tree-wide: use isatty_safe() everywhere 2024-10-25 14:09:38 +02:00
Nick Rosbrook
0eacbc1240 networkd-test: add basic tests for systemd-networkd-wait-online --dns 2024-10-24 16:24:13 -04:00
Nick Rosbrook
2405c6c278 test: add test for resolved SubscribeDNSConfiguration API 2024-10-24 16:24:13 -04:00
Nick Rosbrook
0ceda51f91 test: cleanup after testcase_12_resolvectl2 2024-10-24 16:24:13 -04:00
Nick Rosbrook
f9764f05e4 wait-online: add support for waiting for DNS configuration
Add a new flag to systemd-networkd-wait-online, --dns, to allow waiting
for DNS to be configured.

DNS is considered configured when at least one DNS server is accessible.
If a link has search domains configured, or has DNSDefaultRoute=yes,
then at least one link-specific DNS server may be accessible. Otherwise,
global DNS servers may be considered.
2024-10-24 16:24:13 -04:00
Nick Rosbrook
0a2ec4a0b3 wait-online: keep track of link DNSDefaultRoute=
This will be used in a later commit.
2024-10-24 15:58:52 -04:00
Nick Rosbrook
04c6160684 json-util: add json_dispatch_in_addr_union
This will be used in a later commit.
2024-10-24 15:58:52 -04:00
Nick Rosbrook
f503b73855 resolved: add SubscribeDNSConfiguration to varlink API
Add a new method to io.systemd.Resolve.Monitor that allows subscribing
to changes in the systemd-resolved DNS configuration. The new method
emits the full DNS configuration (one entry for global configuration,
and one entry for each interface), any time the configuration is
updated.
2024-10-24 15:58:51 -04:00
Nick Rosbrook
c292ae6eca resolved: add a helper to check if DNS server is accessible
We check this by opening a UDP socket, binding it to the associated link
if there is one, and attempting to connect. We do not send any traffic
on it, but this will tell us if there are routes to the DNS server.

This will be used in a later commit.
2024-10-24 14:54:30 -04:00
Nick Rosbrook
5d79dde7cf varlink-util: add varlink_many_notify
We already have varlink_many_notifyb. Just re-factor it slightly and add
a plain varlink_many_notify.
2024-10-24 14:54:30 -04:00
Nick Rosbrook
56078802c1 resolve: rename varlink_subscription -> varlink_query_results_subscription
No functional change. Make it more clear that these varlink connections
are subscribed to query results. This prepares for adding SubscribeDNS
to the varlink API.
2024-10-24 14:54:30 -04:00
35 changed files with 1176 additions and 167 deletions

3
TODO
View File

@ -162,9 +162,6 @@ Features:
sd_event_add_child(), and then get rid of many more explicit sigprocmask()
calls.
* maybe set shell.prompt.prefix credential in run0 to some warning emoji,
i.e. ⚠️ or ☢️ or ⚡ or 👊 or 🧑‍🔧 or so.
* introduce new structure Tpm2CombinedPolicy, that combines the various TPm2
policy bits into one structure, i.e. public key info, pcr masks, pcrlock
stuff, pin and so on. Then pass that around in tpm2_seal() and tpm2_unseal().

View File

@ -192,6 +192,35 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--pty</option></term>
<term><option>--pipe</option></term>
<listitem><para>Request allocation of a pseudo TTY for the <command>run0</command> session (in case
of <option>--pty</option>), or request passing the caller's STDIO file descriptors directly through
(in case of <option>--pipe</option>). If neither switch is specified, or if both switches are
specified, the mode will be picked automatically: if standard input, standard output and standard
error output are all connected to a TTY then a pseudo TTY is allocated, otherwise the relevant file
descriptors are passed through directly.</para>
<xi:include href="version-info.xml" xpointer="v257"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--shell-prompt-prefix=<replaceable>STRING</replaceable></option></term>
<listitem><para>Set a shell prompt prefix string. This ultimately controls the
<varname>$SHELL_PROMPT_PREFIX</varname> environment variable for the invoked program, which is
typically imported into the shell prompt. By default if emojis are supported a superhero emoji is
shown (🦸). This default may also be changed (or turned off) by passing the
<varname>$SYSTEMD_RUN_SHELL_PROMPT_PREFIX</varname> environment variable to <varname>run0</varname>,
see below. Set to an empty string to disable shell prompt prefixing.</para>
<xi:include href="version-info.xml" xpointer="v257"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--machine=</option></term>
@ -256,7 +285,30 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>$SHELL_PROMPT_PREFIX</varname></term>
<listitem><para>By default set to the superhero emoji (if supported), but may be overriden with the
<varname>$SYSTEMD_RUN_SHELL_PROMPT_PREFIX</varname> environment variable (see below), or the
<option>--shell-prompt-prefix=</option> switch (see above).</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
</variablelist>
<para>The following variables may be passed to <command>run0</command>:</para>
<variablelist>
<varlistentry>
<term><varname>$SYSTEMD_RUN_SHELL_PROMPT_PREFIX</varname></term>
<listitem><para>If set, overrides the default shell prompt prefix that <command>run0</command> sets
for the invoked shell (the superhero emoji). Set to an empty string to disable shell prompt
prefixing.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>

View File

@ -144,6 +144,20 @@
<xi:include href="version-info.xml" xpointer="v249"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--dns</option></term>
<listitem><para>Waiting for a DNS server of each network interface to be accessible. If this
option is specified with <option>--any</option>, then
<command>systemd-networkd-wait-online</command> exits with success when at least one interface
becomes online and has an accessible DNS server.</para>
<para>If there are search domains configured on a link, or if a link has <varname>DNSDefaultRoute=yes</varname>,
then there must be an accessible link-specific DNS server configured. Otherwise, global DNS servers
may be considered.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--any</option></term>

View File

@ -80,6 +80,7 @@ const char* special_glyph_full(SpecialGlyph code, bool force_utf) {
[SPECIAL_GLYPH_YELLOW_CIRCLE] = "o",
[SPECIAL_GLYPH_BLUE_CIRCLE] = "o",
[SPECIAL_GLYPH_GREEN_CIRCLE] = "o",
[SPECIAL_GLYPH_SUPERHERO] = "S",
},
/* UTF-8 */
@ -149,6 +150,7 @@ const char* special_glyph_full(SpecialGlyph code, bool force_utf) {
[SPECIAL_GLYPH_YELLOW_CIRCLE] = u8"🟡",
[SPECIAL_GLYPH_BLUE_CIRCLE] = u8"🔵",
[SPECIAL_GLYPH_GREEN_CIRCLE] = u8"🟢",
[SPECIAL_GLYPH_SUPERHERO] = u8"🦸",
},
};

View File

@ -55,6 +55,7 @@ typedef enum SpecialGlyph {
SPECIAL_GLYPH_YELLOW_CIRCLE,
SPECIAL_GLYPH_BLUE_CIRCLE,
SPECIAL_GLYPH_GREEN_CIRCLE,
SPECIAL_GLYPH_SUPERHERO,
_SPECIAL_GLYPH_MAX,
_SPECIAL_GLYPH_INVALID = -EINVAL,
} SpecialGlyph;

View File

@ -124,6 +124,22 @@ int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_di
return 0;
}
int json_dispatch_in_addr_union(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
union in_addr_union *address = ASSERT_PTR(userdata);
_cleanup_(iovec_done) struct iovec iov = {};
int r;
r = json_dispatch_byte_array_iovec(name, variant, flags, &iov);
if (r < 0)
return r;
if (!IN_SET(iov.iov_len, sizeof(struct in_addr), sizeof(struct in6_addr)))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name));
memcpy(address, iov.iov_base, iov.iov_len);
return 0;
}
int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
char **p = ASSERT_PTR(userdata);
const char *path;

View File

@ -112,6 +112,7 @@ int json_dispatch_byte_array_iovec(const char *name, sd_json_variant *variant, s
int json_dispatch_user_group_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_const_user_group_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_in_addr_union(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_pidref(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_devnum(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);

View File

@ -86,6 +86,19 @@ int varlink_callb_and_log(
return varlink_call_and_log(v, method, parameters, ret_parameters);
}
int varlink_many_notify(Set *s, sd_json_variant *parameters) {
sd_varlink *link;
int r = 1;
if (set_isempty(s))
return 0;
SET_FOREACH(link, s)
RET_GATHER(r, sd_varlink_notify(link, parameters));
return r;
}
int varlink_many_notifyb(Set *s, ...) {
int r;
@ -102,12 +115,7 @@ int varlink_many_notifyb(Set *s, ...) {
if (r < 0)
return r;
r = 1;
sd_varlink *link;
SET_FOREACH(link, s)
RET_GATHER(r, sd_varlink_notify(link, parameters));
return r;
return varlink_many_notify(s, parameters);
}
int varlink_many_reply(Set *s, sd_json_variant *parameters) {

View File

@ -13,6 +13,7 @@ int varlink_callb_and_log(sd_varlink *v, const char *method, sd_json_variant **r
#define varlink_callbo_and_log(v, method, ret_parameters, ...) \
varlink_callb_and_log((v), (method), (ret_parameters), SD_JSON_BUILD_OBJECT(__VA_ARGS__))
int varlink_many_notify(Set *s, sd_json_variant *parameters);
int varlink_many_notifyb(Set *s, ...);
#define varlink_many_notifybo(s, ...) \
varlink_many_notifyb((s), SD_JSON_BUILD_OBJECT(__VA_ARGS__))

View File

@ -8,7 +8,7 @@
#include "operation.h"
#include "process-util.h"
static int operation_done_internal(const siginfo_t *si, Operation *o, sd_bus_error *error) {
static int read_operation_errno(const siginfo_t *si, Operation *o) {
int r;
assert(si);
@ -27,15 +27,6 @@ static int operation_done_internal(const siginfo_t *si, Operation *o, sd_bus_err
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Received unexpectedly short message when reading operation's errno");
}
if (o->done)
/* A completion routine is set for this operation, call it. */
return o->done(o, r, error);
/* The default operation when done is to simply return an error on failure or an empty success
* message on success. */
if (r < 0)
log_debug_errno(r, "Operation failed: %m");
return r;
}
@ -51,10 +42,18 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat
o->pid = 0;
r = read_operation_errno(si, o);
if (r < 0)
log_debug_errno(r, "Operation failed: %m");
/* If a completion routine (o->done) is set for this operation, call it. It sends a response, but can return an error in which case it expect us to reply.
* Otherwise, the default action is to simply return an error on failure or an empty success message on success. */
if (o->message) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
if (o->done)
r = o->done(o, r, &error);
r = operation_done_internal(si, o, &error);
if (r < 0) {
if (!sd_bus_error_is_set(&error))
sd_bus_error_set_errno(&error, r);
@ -62,16 +61,20 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat
r = sd_bus_reply_method_error(o->message, &error);
if (r < 0)
log_error_errno(r, "Failed to reply to dbus message: %m");
} else {
} else if (!o->done) {
/* when o->done set it's responsible for sending reply in a happy-path case */
r = sd_bus_reply_method_return(o->message, NULL);
if (r < 0)
log_error_errno(r, "Failed to reply to dbus message: %m");
}
} else if (o->link) {
r = operation_done_internal(si, o, /* error = */ NULL);
if (o->done)
r = o->done(o, r, /* error = */ NULL);
if (r < 0)
(void) sd_varlink_error_errno(o->link, r);
else
else if (!o->done)
/* when o->done set it's responsible for sending reply in a happy-path case */
(void) sd_varlink_reply(o->link, NULL);
} else
assert_not_reached();

View File

@ -113,6 +113,7 @@ sources = files(
systemd_networkd_sources = files('networkd.c')
systemd_networkd_wait_online_sources = files(
'wait-online/dns-configuration.c',
'wait-online/link.c',
'wait-online/manager.c',
'wait-online/wait-online.c',

View File

@ -0,0 +1,131 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "alloc-util.h"
#include "dns-configuration.h"
#include "json-util.h"
#include "set.h"
#include "strv.h"
DNSServer *dns_server_free(DNSServer *s) {
if (!s)
return NULL;
free(s->server_name);
return mfree(s);
}
static int dispatch_dns_server(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dns_server_dispatch_table[] = {
{ "address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr_union, offsetof(DNSServer, addr), SD_JSON_MANDATORY },
{ "addressFamily", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(DNSServer, family), SD_JSON_MANDATORY },
{ "port", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint16, offsetof(DNSServer, port), 0 },
{ "interfaceSpecifier", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(DNSServer, ifindex), 0 },
{ "serverName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSServer, server_name), 0 },
{ "accessible", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DNSServer, accessible), SD_JSON_MANDATORY },
{},
};
DNSServer **ret = ASSERT_PTR(userdata);
_cleanup_(dns_server_freep) DNSServer *s = NULL;
int r;
s = new0(DNSServer, 1);
if (!s)
return log_oom();
r = sd_json_dispatch(variant, dns_server_dispatch_table, flags, s);
if (r < 0)
return r;
*ret = TAKE_PTR(s);
return 0;
}
static int dispatch_dns_server_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
Set **ret = ASSERT_PTR(userdata);
Set *dns_servers = set_new(NULL);
sd_json_variant *v = NULL;
int r;
JSON_VARIANT_ARRAY_FOREACH(v, variant) {
_cleanup_(dns_server_freep) DNSServer *s = NULL;
s = new0(DNSServer, 1);
if (!s)
return log_oom();
r = dispatch_dns_server(name, v, flags, &s);
if (r < 0)
return json_log(v, flags, r, "JSON array element is not a valid DNSServer.");
r = set_put(dns_servers, TAKE_PTR(s));
if (r < 0)
return log_oom();
}
set_free_and_replace(*ret, dns_servers);
return 0;
}
DNSConfiguration *dns_configuration_free(DNSConfiguration *c) {
if (!c)
return NULL;
dns_server_free(c->current_dns_server);
set_free_with_destructor(c->dns_servers, dns_server_free);
free(c->ifname);
strv_free(c->search_domains);
return mfree(c);
}
static int dispatch_dns_configuration(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dns_configuration_dispatch_table[] = {
{ "interface", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSConfiguration, ifname), 0 },
{ "interfaceIndex", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(DNSConfiguration, ifindex), 0 },
{ "currentDNSServer", SD_JSON_VARIANT_OBJECT, dispatch_dns_server, offsetof(DNSConfiguration, current_dns_server), 0 },
{ "dnsServers", SD_JSON_VARIANT_ARRAY, dispatch_dns_server_array, offsetof(DNSConfiguration, dns_servers), 0 },
{ "searchDomains", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(DNSConfiguration, search_domains), 0 },
{},
};
DNSConfiguration **ret = ASSERT_PTR(userdata);
_cleanup_(dns_configuration_freep) DNSConfiguration *c = NULL;
int r;
c = new0(DNSConfiguration, 1);
if (!c)
return log_oom();
r = sd_json_dispatch(variant, dns_configuration_dispatch_table, flags, c);
if (r < 0)
return r;
*ret = TAKE_PTR(c);
return 0;
}
int dns_configuration_from_json(sd_json_variant *variant, DNSConfiguration **ret) {
return dispatch_dns_configuration(NULL, variant, SD_JSON_LOG, ret);
}
bool dns_is_accessible(DNSConfiguration *c) {
DNSServer *s = NULL;
if (!c)
return false;
if (c->current_dns_server && c->current_dns_server->accessible)
return true;
SET_FOREACH(s, c->dns_servers)
if (s->accessible)
return true;
return false;
}

View File

@ -0,0 +1,37 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-json.h"
#include "in-addr-util.h"
#include "macro-fundamental.h"
#include "set.h"
typedef struct DNSServer DNSServer;
typedef struct DNSConfiguration DNSConfiguration;
struct DNSServer {
union in_addr_union addr;
int family;
uint16_t port;
int ifindex;
char *server_name;
bool accessible;
};
DNSServer *dns_server_free(DNSServer *s);
DEFINE_TRIVIAL_CLEANUP_FUNC(DNSServer*, dns_server_free);
struct DNSConfiguration {
char *ifname;
int ifindex;
DNSServer *current_dns_server;
Set *dns_servers;
char **search_domains;
};
int dns_configuration_from_json(sd_json_variant *variant, DNSConfiguration **ret);
bool dns_is_accessible(DNSConfiguration *c);
DNSConfiguration *dns_configuration_free(DNSConfiguration *c);
DEFINE_TRIVIAL_CLEANUP_FUNC(DNSConfiguration*, dns_configuration_free);

View File

@ -3,6 +3,7 @@
#include "sd-network.h"
#include "alloc-util.h"
#include "dns-configuration.h"
#include "format-ifname.h"
#include "hashmap.h"
#include "link.h"
@ -62,6 +63,8 @@ Link *link_free(Link *l) {
hashmap_remove(l->manager->links_by_name, *n);
}
dns_configuration_free(l->dns_configuration);
free(l->state);
free(l->ifname);
strv_free(l->altnames);
@ -246,5 +249,11 @@ int link_update_monitor(Link *l) {
else
free_and_replace(l->state, state);
r = sd_network_link_get_dns_default_route(l->ifindex);
if (r < 0)
ret = log_link_debug_errno(l, r, "Failed to get DNS default route, ignoring: %m");
else
l->dns_default_route = r > 0;
return ret;
}

View File

@ -3,6 +3,7 @@
#include "sd-netlink.h"
#include "dns-configuration.h"
#include "log-link.h"
#include "network-util.h"
@ -24,6 +25,8 @@ struct Link {
LinkAddressState ipv4_address_state;
LinkAddressState ipv6_address_state;
char *state;
bool dns_default_route;
DNSConfiguration *dns_configuration;
};
int link_new(Manager *m, Link **ret, int ifindex, const char *ifname);

View File

@ -4,7 +4,13 @@
#include <linux/if.h>
#include <fnmatch.h>
#include "sd-event.h"
#include "sd-json.h"
#include "sd-varlink.h"
#include "alloc-util.h"
#include "dns-configuration.h"
#include "json-util.h"
#include "link.h"
#include "manager.h"
#include "netlink-util.h"
@ -133,6 +139,28 @@ static int manager_link_is_online(Manager *m, Link *l, const LinkOperationalStat
"No routable IPv6 address is configured.");
}
if (m->requires_dns) {
bool require_link_dns = false;
if (l->dns_configuration)
require_link_dns = !strv_isempty(l->dns_configuration->search_domains);
require_link_dns = require_link_dns || l->dns_default_route;
if (!dns_is_accessible(l->dns_configuration)) {
/* When the link is configured with DNSDefaultRoute=yes, or there are search domains
* configured for the link, we require that link-specific DNS servers are configured.
* Otherwise, we are free to fallback to global DNS. */
if (require_link_dns)
return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL),
"No link-specific DNS server is accessible.");
if (!dns_is_accessible(m->dns_configuration))
return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL),
"No DNS server is accessible.");
}
}
log_link_debug(l, "link is configured by networkd and online.");
return true;
}
@ -381,13 +409,102 @@ static int manager_network_monitor_listen(Manager *m) {
return 0;
}
static int on_dns_configuration_event(
sd_varlink *link,
sd_json_variant *parameters,
const char *error_id,
sd_varlink_reply_flags_t flags,
void *userdata) {
Manager *m = ASSERT_PTR(userdata);
sd_json_variant *configurations = NULL, *v = NULL;
int r;
assert(link);
if (error_id) {
log_warning("DNS configuration event error, ignoring: %s", error_id);
return 0;
}
configurations = sd_json_variant_by_key(parameters, "configuration");
if (!configurations || !sd_json_variant_is_array(configurations)) {
log_warning("DNS configuration JSON data does not have configuration key, ignoring.");
return 0;
}
JSON_VARIANT_ARRAY_FOREACH(v, configurations) {
_cleanup_(dns_configuration_freep) DNSConfiguration *c = NULL;
r = dns_configuration_from_json(v, &c);
if (r < 0) {
log_warning_errno(r, "Failed to get DNS configuration JSON, ignoring: %m");
continue;
}
if (c->ifindex > 0) {
Link *l = hashmap_get(m->links_by_index, INT_TO_PTR(c->ifindex));
if (l)
free_and_replace_full(l->dns_configuration, c, dns_configuration_free);
} else
/* Global DNS configuration */
free_and_replace_full(m->dns_configuration, c, dns_configuration_free);
}
if (manager_configured(m))
sd_event_exit(m->event, 0);
return 0;
}
static int manager_dns_configuration_listen(Manager *m) {
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
int r;
assert(m);
assert(m->event);
if (!m->requires_dns)
return 0;
r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor");
if (r < 0)
return log_error_errno(r, "Failed to connect to io.systemd.Resolve.Monitor: %m");
r = sd_varlink_set_relative_timeout(vl, USEC_INFINITY);
if (r < 0)
return log_error_errno(r, "Failed to set varlink timeout: %m");
r = sd_varlink_attach_event(vl, m->event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
(void) sd_varlink_set_userdata(vl, m);
r = sd_varlink_bind_reply(vl, on_dns_configuration_event);
if (r < 0)
return log_error_errno(r, "Failed to bind varlink reply callback: %m");
r = sd_varlink_observebo(
vl,
"io.systemd.Resolve.Monitor.SubscribeDNSConfiguration",
SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", false));
if (r < 0)
return log_error_errno(r, "Failed to issue SubscribeDNSConfiguration: %m");
m->varlink_client = TAKE_PTR(vl);
return 0;
}
int manager_new(Manager **ret,
Hashmap *command_line_interfaces_by_name,
char **ignored_interfaces,
LinkOperationalStateRange required_operstate,
AddressFamily required_family,
bool any,
usec_t timeout) {
usec_t timeout,
bool requires_dns) {
_cleanup_(manager_freep) Manager *m = NULL;
int r;
@ -404,6 +521,7 @@ int manager_new(Manager **ret,
.required_operstate = required_operstate,
.required_family = required_family,
.any = any,
.requires_dns = requires_dns,
};
r = sd_event_default(&m->event);
@ -428,6 +546,10 @@ int manager_new(Manager **ret,
if (r < 0)
return r;
r = manager_dns_configuration_listen(m);
if (r < 0)
return r;
*ret = TAKE_PTR(m);
return 0;
@ -445,6 +567,9 @@ Manager* manager_free(Manager *m) {
sd_event_source_unref(m->rtnl_event_source);
sd_netlink_unref(m->rtnl);
sd_event_unref(m->event);
sd_varlink_unref(m->varlink_client);
dns_configuration_free(m->dns_configuration);
return mfree(m);
}

View File

@ -4,7 +4,9 @@
#include "sd-event.h"
#include "sd-netlink.h"
#include "sd-network.h"
#include "sd-varlink.h"
#include "dns-configuration.h"
#include "hashmap.h"
#include "network-util.h"
#include "time-util.h"
@ -23,6 +25,7 @@ struct Manager {
LinkOperationalStateRange required_operstate;
AddressFamily required_family;
bool any;
bool requires_dns;
sd_netlink *rtnl;
sd_event_source *rtnl_event_source;
@ -31,13 +34,16 @@ struct Manager {
sd_event_source *network_monitor_event_source;
sd_event *event;
sd_varlink *varlink_client;
DNSConfiguration *dns_configuration;
};
Manager* manager_free(Manager *m);
int manager_new(Manager **ret, Hashmap *command_line_interfaces_by_name, char **ignored_interfaces,
LinkOperationalStateRange required_operstate,
AddressFamily required_family,
bool any, usec_t timeout);
bool any, usec_t timeout, bool requires_dns);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);

View File

@ -22,6 +22,7 @@ static char **arg_ignore = NULL;
static LinkOperationalStateRange arg_required_operstate = LINK_OPERSTATE_RANGE_INVALID;
static AddressFamily arg_required_family = ADDRESS_FAMILY_NO;
static bool arg_any = false;
static bool arg_requires_dns = false;
STATIC_DESTRUCTOR_REGISTER(arg_interfaces, hashmap_free_free_freep);
STATIC_DESTRUCTOR_REGISTER(arg_ignore, strv_freep);
@ -48,6 +49,7 @@ static int help(void) {
" -6 --ipv6 Requires at least one IPv6 address\n"
" --any Wait until at least one of the interfaces is online\n"
" --timeout=SECS Maximum time to wait for network connectivity\n"
" --dns Requires at least one DNS server to be accessible\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
link);
@ -106,6 +108,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_IGNORE,
ARG_ANY,
ARG_TIMEOUT,
ARG_DNS,
};
static const struct option options[] = {
@ -119,6 +122,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "ipv6", no_argument, NULL, '6' },
{ "any", no_argument, NULL, ARG_ANY },
{ "timeout", required_argument, NULL, ARG_TIMEOUT },
{ "dns", no_argument, NULL, ARG_DNS },
{}
};
@ -178,6 +182,10 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_DNS:
arg_requires_dns = true;
break;
case '?':
return -EINVAL;
@ -204,7 +212,14 @@ static int run(int argc, char *argv[]) {
if (arg_quiet)
log_set_max_level(LOG_ERR);
r = manager_new(&m, arg_interfaces, arg_ignore, arg_required_operstate, arg_required_family, arg_any, arg_timeout);
r = manager_new(&m,
arg_interfaces,
arg_ignore,
arg_required_operstate,
arg_required_family,
arg_any,
arg_timeout,
arg_requires_dns);
if (r < 0)
return log_error_errno(r, "Could not create manager: %m");

View File

@ -290,7 +290,7 @@ static int handle_arg_console(const char *arg) {
else if (streq(arg, "passive"))
arg_console_mode = CONSOLE_PASSIVE;
else if (streq(arg, "pipe")) {
if (isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO))
if (isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO))
log_full(arg_quiet ? LOG_DEBUG : LOG_NOTICE,
"Console mode 'pipe' selected, but standard input/output are connected to an interactive TTY. "
"Most likely you want to use 'interactive' console mode for proper interactivity and shell job control. "
@ -298,7 +298,7 @@ static int handle_arg_console(const char *arg) {
arg_console_mode = CONSOLE_PIPE;
} else if (streq(arg, "autopipe")) {
if (isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO))
if (isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO))
arg_console_mode = CONSOLE_INTERACTIVE;
else
arg_console_mode = CONSOLE_PIPE;
@ -5981,7 +5981,7 @@ static int run(int argc, char *argv[]) {
umask(0022);
if (arg_console_mode < 0)
arg_console_mode = isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO) ?
arg_console_mode = isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO) ?
CONSOLE_INTERACTIVE : CONSOLE_READ_ONLY;
if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */

View File

@ -150,6 +150,17 @@
<annotate key="org.freedesktop.policykit.owner">unix-user:systemd-resolve</annotate>
</action>
<action id="org.freedesktop.resolve1.subscribe-dns-configuration">
<description gettext-domain="systemd">Subscribe to DNS configuration</description>
<message gettext-domain="systemd">Authentication is required to subscribe to DNS configuration.</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.owner">unix-user:systemd-resolve</annotate>
</action>
<action id="org.freedesktop.resolve1.dump-cache">
<description gettext-domain="systemd">Dump cache</description>
<message gettext-domain="systemd">Authentication is required to dump cache.</message>

View File

@ -1,14 +1,20 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <net/if_arp.h>
#include "sd-messages.h"
#include "alloc-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "json-util.h"
#include "resolved-bus.h"
#include "resolved-dns-server.h"
#include "resolved-dns-stub.h"
#include "resolved-manager.h"
#include "resolved-resolv-conf.h"
#include "siphash24.h"
#include "socket-util.h"
#include "string-table.h"
#include "string-util.h"
@ -69,6 +75,7 @@ int dns_server_new(
.ifindex = ifindex,
.server_name = TAKE_PTR(name),
.config_source = config_source,
.accessible = -1,
};
dns_server_reset_features(s);
@ -875,6 +882,7 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) {
dns_cache_flush(&m->unicast_scope->cache);
(void) manager_send_changed(m, "CurrentDNSServer");
(void) manager_send_dns_configuration_changed(m);
return s;
}
@ -1128,3 +1136,58 @@ int dns_server_dump_state_to_json(DnsServer *server, sd_json_variant **ret) {
SD_JSON_BUILD_PAIR_BOOLEAN("PacketInvalid", server->packet_invalid),
SD_JSON_BUILD_PAIR_BOOLEAN("PacketDoOff", server->packet_do_off));
}
int dns_server_is_accessible(DnsServer *server) {
_cleanup_close_ int fd = -EBADF;
union sockaddr_union sa;
int ifindex, r;
assert(server);
if (server->accessible >= 0)
return server->accessible;
r = sockaddr_set_in_addr(&sa, server->family, &server->address, dns_server_port(server));
if (r < 0)
return r;
fd = socket(server->family, SOCK_DGRAM|SOCK_CLOEXEC, 0);
if (fd < 0)
return -errno;
ifindex = dns_server_ifindex(server);
if (ifindex > 0) {
r = socket_bind_to_ifindex(fd, ifindex);
if (r < 0)
return r;
}
r = RET_NERRNO(connect(fd, &sa.sa, SOCKADDR_LEN(sa)));
return (server->accessible = r >= 0);
}
int dns_server_dump_configuration_to_json(DnsServer *server, sd_json_variant **ret) {
bool accessible = false;
int ifindex, r;
assert(server);
assert(ret);
ifindex = dns_server_ifindex(server);
r = dns_server_is_accessible(server);
if (r < 0)
log_debug_errno(r, "Failed to check if %s is accessible, assume not: %m", dns_server_string_full(server));
else
accessible = r;
return sd_json_buildo(
ret,
JSON_BUILD_PAIR_IN_ADDR("address", &server->address, server->family),
SD_JSON_BUILD_PAIR_INTEGER("addressFamily", server->family),
SD_JSON_BUILD_PAIR_UNSIGNED("port", dns_server_port(server)),
SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "interfaceSpecifier", SD_JSON_BUILD_UNSIGNED(ifindex)),
JSON_BUILD_PAIR_STRING_NON_EMPTY("serverName", server->server_name),
SD_JSON_BUILD_PAIR_BOOLEAN("accessible", accessible));
}

View File

@ -106,6 +106,9 @@ struct DnsServer {
/* Servers registered via D-Bus are not removed on reload */
ResolveConfigSource config_source;
/* Tri-state to indicate if the DNS server is accessible. */
int accessible;
};
int dns_server_new(
@ -187,3 +190,6 @@ static inline bool dns_server_is_fallback(DnsServer *s) {
}
int dns_server_dump_state_to_json(DnsServer *server, sd_json_variant **ret);
int dns_server_dump_configuration_to_json(DnsServer *server, sd_json_variant **ret);
int dns_server_is_accessible(DnsServer *server);

View File

@ -293,6 +293,7 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi
(void) link_save_user(l);
(void) manager_write_resolv_conf(l->manager);
(void) manager_send_changed(l->manager, "DNS");
(void) manager_send_dns_configuration_changed(l->manager);
if (j)
log_link_info(l, "Bus client set DNS server list to: %s", j);
@ -768,6 +769,7 @@ int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error
(void) link_save_user(l);
(void) manager_write_resolv_conf(l->manager);
(void) manager_send_changed(l->manager, "DNS");
(void) manager_send_dns_configuration_changed(l->manager);
return sd_bus_reply_method_return(message, NULL);
}

View File

@ -299,6 +299,12 @@ static int link_update_dns_servers(Link *l) {
}
dns_server_unlink_marked(l->dns_servers);
LIST_FOREACH(servers, s, l->dns_servers)
/* If the link state changed, we should re-check if DNS servers
* are accessible. */
s->accessible = -1;
return 0;
clear:

View File

@ -22,6 +22,7 @@
#include "idn-util.h"
#include "io-util.h"
#include "iovec-util.h"
#include "json-util.h"
#include "memstream-util.h"
#include "missing_network.h"
#include "missing_socket.h"
@ -286,6 +287,7 @@ static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *
(void) manager_write_resolv_conf(m);
(void) manager_send_changed(m, "DNS");
(void) manager_send_dns_configuration_changed(m);
return 0;
}
@ -1184,7 +1186,7 @@ int manager_monitor_send(Manager *m, DnsQuery *q) {
assert(m);
if (set_isempty(m->varlink_subscription))
if (set_isempty(m->varlink_query_results_subscription))
return 0;
/* Merge all questions into one */
@ -1234,7 +1236,7 @@ int manager_monitor_send(Manager *m, DnsQuery *q) {
}
r = varlink_many_notifybo(
m->varlink_subscription,
m->varlink_query_results_subscription,
SD_JSON_BUILD_PAIR("state", SD_JSON_BUILD_STRING(dns_transaction_state_to_string(q->state))),
SD_JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_DNSSEC_FAILED,
"result", SD_JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
@ -1932,3 +1934,114 @@ void dns_manager_reset_statistics(Manager *m) {
m->n_failure_responses_served_stale_total = 0;
zero(m->n_dnssec_verdict);
}
static int dns_configuration_json_append(
const char *ifname,
int ifindex,
DnsServer *current_dns_server,
DnsServer *dns_servers,
DnsSearchDomain *search_domains,
sd_json_variant **configuration) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *dns_servers_json = NULL,
*search_domains_json = NULL,
*current_dns_server_json = NULL;
int r;
assert(configuration);
r = sd_json_variant_new_array(&dns_servers_json, NULL, 0);
if (r < 0)
return r;
r = sd_json_variant_new_array(&search_domains_json, NULL, 0);
if (r < 0)
return r;
if (current_dns_server) {
r = dns_server_dump_configuration_to_json(current_dns_server, &current_dns_server_json);
if (r < 0)
return r;
}
LIST_FOREACH(servers, s, dns_servers) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
r = dns_server_dump_configuration_to_json(s, &v);
if (r < 0)
return r;
r = sd_json_variant_append_array(&dns_servers_json, v);
if (r < 0)
return r;
}
LIST_FOREACH(domains, d, search_domains) {
r = sd_json_variant_append_arrayb(&search_domains_json, SD_JSON_BUILD_STRING(DNS_SEARCH_DOMAIN_NAME(d)));
if (r < 0)
return r;
}
return sd_json_variant_append_arraybo(
configuration,
JSON_BUILD_PAIR_STRING_NON_EMPTY("interface", ifname),
SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "interfaceIndex", SD_JSON_BUILD_UNSIGNED(ifindex)),
JSON_BUILD_PAIR_VARIANT_NON_NULL("currentDNSServer", current_dns_server_json),
SD_JSON_BUILD_PAIR_VARIANT("dnsServers", dns_servers_json),
SD_JSON_BUILD_PAIR_VARIANT("searchDomains", search_domains_json));
}
int manager_dump_dns_configuration_json(Manager *m, sd_json_variant **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL;
Link *l;
int r;
assert(m);
assert(ret);
/* Global DNS configuration */
r = dns_configuration_json_append(
/* ifname = */ NULL,
/* ifindex = */ 0,
manager_get_dns_server(m),
m->dns_servers,
m->search_domains,
&configuration);
if (r < 0)
return r;
/* Append configuration for each link */
HASHMAP_FOREACH(l, m->links) {
r = dns_configuration_json_append(
l->ifname,
l->ifindex,
link_get_dns_server(l),
l->dns_servers,
l->search_domains,
&configuration);
if (r < 0)
return r;
}
return sd_json_buildo(ret, SD_JSON_BUILD_PAIR_VARIANT("configuration", configuration));
}
int manager_send_dns_configuration_changed(Manager *m) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL;
int r;
assert(m);
if (set_isempty(m->varlink_dns_configuration_subscription))
return 0;
r = manager_dump_dns_configuration_json(m, &configuration);
if (r < 0)
return log_warning_errno(r, "Failed to dump DNS configuration json: %m");
r = varlink_many_notify(m->varlink_dns_configuration_subscription, configuration);
if (r < 0)
return log_warning_errno(r, "Failed to send DNS configuration event: %m");
return 0;
}

View File

@ -152,7 +152,8 @@ struct Manager {
sd_varlink_server *varlink_server;
sd_varlink_server *varlink_monitor_server;
Set *varlink_subscription;
Set *varlink_query_results_subscription;
Set *varlink_dns_configuration_subscription;
sd_event_source *clock_change_event_source;
@ -225,3 +226,6 @@ int socket_disable_pmtud(int fd, int af);
int dns_manager_dump_statistics_json(Manager *m, sd_json_variant **ret);
void dns_manager_reset_statistics(Manager *m);
int manager_dump_dns_configuration_json(Manager *m, sd_json_variant **ret);
int manager_send_dns_configuration_changed(Manager *m);

View File

@ -125,14 +125,21 @@ static void vl_on_disconnect(sd_varlink_server *s, sd_varlink *link, void *userd
static void vl_on_notification_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
sd_varlink *removed_link = NULL;
assert(s);
assert(link);
sd_varlink *removed_link = set_remove(m->varlink_subscription, link);
removed_link = set_remove(m->varlink_query_results_subscription, link);
if (removed_link) {
sd_varlink_unref(removed_link);
log_debug("%u monitor clients remain active", set_size(m->varlink_subscription));
log_debug("%u query result monitor clients remain active", set_size(m->varlink_query_results_subscription));
}
removed_link = set_remove(m->varlink_dns_configuration_subscription, link);
if (removed_link) {
sd_varlink_unref(removed_link);
log_debug("%u DNS monitor clients remain active", set_size(m->varlink_dns_configuration_subscription));
}
}
@ -1275,12 +1282,12 @@ static int vl_method_subscribe_query_results(sd_varlink *link, sd_json_variant *
if (r < 0)
return log_error_errno(r, "Failed to report monitor to be established: %m");
r = set_ensure_put(&m->varlink_subscription, NULL, link);
r = set_ensure_put(&m->varlink_query_results_subscription, NULL, link);
if (r < 0)
return log_error_errno(r, "Failed to add subscription to set: %m");
sd_varlink_ref(link);
log_debug("%u clients now attached for varlink notifications", set_size(m->varlink_subscription));
log_debug("%u clients now attached for query result varlink notifications", set_size(m->varlink_query_results_subscription));
return 1;
}
@ -1400,6 +1407,38 @@ static int vl_method_reset_statistics(sd_varlink *link, sd_json_variant *paramet
return sd_varlink_replyb(link, SD_JSON_BUILD_EMPTY_OBJECT);
}
static int vl_method_subscribe_dns_configuration(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL;
Manager *m = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link)));
int r;
/* if the client didn't set the more flag, it is using us incorrectly */
if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL);
r = verify_polkit(link, parameters, "org.freedesktop.resolve1.subscribe-dns-configuration");
if (r <= 0)
return r;
r = manager_dump_dns_configuration_json(m, &configuration);
if (r < 0)
return log_error_errno(r, "Failed to dump current DNS configuration: %m");
r = sd_varlink_notify(link, configuration);
if (r < 0)
return log_error_errno(r, "Failed to send current DNS configuration: %m");
r = set_ensure_put(&m->varlink_dns_configuration_subscription, NULL, link);
if (r < 0)
return log_error_errno(r, "Failed to add subscription to set: %m");
sd_varlink_ref(link);
log_debug("%u clients now attached for link configuration varlink notifications",
set_size(m->varlink_dns_configuration_subscription));
return 1;
}
static int varlink_monitor_server_init(Manager *m) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL;
int r;
@ -1425,7 +1464,8 @@ static int varlink_monitor_server_init(Manager *m) {
"io.systemd.Resolve.Monitor.DumpCache", vl_method_dump_cache,
"io.systemd.Resolve.Monitor.DumpServerState", vl_method_dump_server_state,
"io.systemd.Resolve.Monitor.DumpStatistics", vl_method_dump_statistics,
"io.systemd.Resolve.Monitor.ResetStatistics", vl_method_reset_statistics);
"io.systemd.Resolve.Monitor.ResetStatistics", vl_method_reset_statistics,
"io.systemd.Resolve.Monitor.SubscribeDNSConfiguration", vl_method_subscribe_dns_configuration);
if (r < 0)
return log_error_errno(r, "Failed to register varlink methods: %m");

View File

@ -22,6 +22,7 @@
#include "chase.h"
#include "env-util.h"
#include "escape.h"
#include "event-util.h"
#include "exec-util.h"
#include "exit-status.h"
#include "fd-util.h"
@ -85,6 +86,7 @@ static char *arg_exec_path = NULL;
static bool arg_ignore_failure = false;
static char *arg_background = NULL;
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
static char *arg_shell_prompt_prefix = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_description, freep);
STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep);
@ -96,6 +98,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_working_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_exec_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
STATIC_DESTRUCTOR_REGISTER(arg_shell_prompt_prefix, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@ -171,6 +174,10 @@ static int help_sudo_mode(void) {
if (r < 0)
return log_oom();
/* NB: Let's not go overboard with short options: we try to keep a modicum of compatibility with
* sudo's short switches, hence please do not introduce new short switches unless they have a roughly
* equivalent purpose on sudo. Use long options for everything private to run0. */
printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n"
"\n%sElevate privileges interactively.%s\n\n"
" -h --help Show this help\n"
@ -188,6 +195,9 @@ static int help_sudo_mode(void) {
" -D --chdir=PATH Set working directory\n"
" --setenv=NAME[=VALUE] Set environment variable\n"
" --background=COLOR Set ANSI color for background\n"
" --pty Request allocation of a pseudo TTY for stdio\n"
" --pipe Request direct pipe for stdio\n"
" --shell-prompt-prefix=PREFIX Set $SHELL_PROMPT_PREFIX\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@ -674,7 +684,7 @@ static int parse_argv(int argc, char *argv[]) {
/* If we both --pty and --pipe are specified we'll automatically pick --pty if we are connected fully
* to a TTY and pick direct fd passing otherwise. This way, we automatically adapt to usage in a shell
* pipeline, but we are neatly interactive with tty-level isolation otherwise. */
arg_stdio = isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ?
arg_stdio = isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO) && isatty_safe(STDERR_FILENO) ?
ARG_STDIO_PTY :
ARG_STDIO_DIRECT;
@ -770,27 +780,33 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
ARG_NICE,
ARG_SETENV,
ARG_BACKGROUND,
ARG_PTY,
ARG_PIPE,
ARG_SHELL_PROMPT_PREFIX,
};
/* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions
* though (but limit the extension to long options). */
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "machine", required_argument, NULL, ARG_MACHINE },
{ "unit", required_argument, NULL, ARG_UNIT },
{ "property", required_argument, NULL, ARG_PROPERTY },
{ "description", required_argument, NULL, ARG_DESCRIPTION },
{ "slice", required_argument, NULL, ARG_SLICE },
{ "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
{ "user", required_argument, NULL, 'u' },
{ "group", required_argument, NULL, 'g' },
{ "nice", required_argument, NULL, ARG_NICE },
{ "chdir", required_argument, NULL, 'D' },
{ "setenv", required_argument, NULL, ARG_SETENV },
{ "background", required_argument, NULL, ARG_BACKGROUND },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "machine", required_argument, NULL, ARG_MACHINE },
{ "unit", required_argument, NULL, ARG_UNIT },
{ "property", required_argument, NULL, ARG_PROPERTY },
{ "description", required_argument, NULL, ARG_DESCRIPTION },
{ "slice", required_argument, NULL, ARG_SLICE },
{ "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
{ "user", required_argument, NULL, 'u' },
{ "group", required_argument, NULL, 'g' },
{ "nice", required_argument, NULL, ARG_NICE },
{ "chdir", required_argument, NULL, 'D' },
{ "setenv", required_argument, NULL, ARG_SETENV },
{ "background", required_argument, NULL, ARG_BACKGROUND },
{ "pty", no_argument, NULL, ARG_PTY },
{ "pipe", no_argument, NULL, ARG_PIPE },
{ "shell-prompt-prefix", required_argument, NULL, ARG_SHELL_PROMPT_PREFIX },
{},
};
@ -883,6 +899,26 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
break;
case ARG_PTY:
if (IN_SET(arg_stdio, ARG_STDIO_DIRECT, ARG_STDIO_AUTO)) /* if --pipe is already used, upgrade to auto mode */
arg_stdio = ARG_STDIO_AUTO;
else
arg_stdio = ARG_STDIO_PTY;
break;
case ARG_PIPE:
if (IN_SET(arg_stdio, ARG_STDIO_PTY, ARG_STDIO_AUTO)) /* If --pty is already used, upgrade to auto mode */
arg_stdio = ARG_STDIO_AUTO;
else
arg_stdio = ARG_STDIO_DIRECT;
break;
case ARG_SHELL_PROMPT_PREFIX:
r = free_and_strdup_warn(&arg_shell_prompt_prefix, optarg);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
@ -913,7 +949,9 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
arg_wait = true;
arg_aggressive_gc = true;
arg_stdio = isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT;
if (IN_SET(arg_stdio, ARG_STDIO_NONE, ARG_STDIO_AUTO))
arg_stdio = isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO) && isatty_safe(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT;
arg_expand_environment = false;
arg_send_sighup = true;
@ -993,6 +1031,25 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
log_debug_errno(r, "Unable to get terminal background color, not tinting background: %m");
}
if (!arg_shell_prompt_prefix) {
const char *e = secure_getenv("SYSTEMD_RUN_SHELL_PROMPT_PREFIX");
if (e) {
arg_shell_prompt_prefix = strdup(e);
if (!arg_shell_prompt_prefix)
return log_oom();
} else if (emoji_enabled()) {
arg_shell_prompt_prefix = strjoin(special_glyph(SPECIAL_GLYPH_SUPERHERO), " ");
if (!arg_shell_prompt_prefix)
return log_oom();
}
}
if (!isempty(arg_shell_prompt_prefix)) {
r = strv_env_assign(&arg_environment, "SHELL_PROMPT_PREFIX", arg_shell_prompt_prefix);
if (r < 0)
return log_error_errno(r, "Failed to set $SHELL_PROMPT_PREFIX environment variable: %m");
}
return 1;
}
@ -1181,7 +1238,7 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
if (r < 0)
return bus_log_create_error(r);
send_term = isatty_safe(STDIN_FILENO) || isatty(STDOUT_FILENO) || isatty(STDERR_FILENO);
send_term = isatty_safe(STDIN_FILENO) || isatty_safe(STDOUT_FILENO) || isatty_safe(STDERR_FILENO);
}
if (send_term) {
@ -1345,78 +1402,75 @@ static int transient_timer_set_properties(sd_bus_message *m) {
return 0;
}
static int make_unit_name(sd_bus *bus, UnitType t, char **ret) {
unsigned soft_reboots_count = 0;
const char *unique, *id;
char *p;
static int make_unit_name(UnitType t, char **ret) {
int r;
assert(bus);
assert(t >= 0);
assert(t < _UNIT_TYPE_MAX);
assert(ret);
r = sd_bus_get_unique_name(bus, &unique);
/* Preferably use our PID + pidfd ID as identifier, if available. It's a boot time unique identifier
* managed by the kernel. Unfortunately only new kernels support this, hence we keep some fallback
* logic in place. */
_cleanup_(pidref_done) PidRef self = PIDREF_NULL;
r = pidref_set_self(&self);
if (r < 0)
return log_error_errno(r, "Failed to get reference to my own process: %m");
r = pidref_acquire_pidfd_id(&self);
if (r < 0) {
log_debug_errno(r, "Failed to acquire pidfd ID of myself, defaulting to randomized unit name: %m");
/* We couldn't get the pidfd id. In that case, just pick a random uuid as name */
sd_id128_t rnd;
/* We couldn't get the unique name, which is a pretty
* common case if we are connected to systemd
* directly. In that case, just pick a random uuid as
* name */
r = sd_id128_randomize(&rnd);
if (r < 0)
return log_error_errno(r, "Failed to generate random run unit name: %m");
if (asprintf(ret, "run-r" SD_ID128_FORMAT_STR ".%s", SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0)
return log_oom();
r = asprintf(ret, "run-r" SD_ID128_FORMAT_STR ".%s", SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t));
} else
r = asprintf(ret, "run-p" PID_FMT "-i%" PRIu64 ".%s", self.pid, self.fd_id, unit_type_to_string(t));
if (r < 0)
return log_oom();
return 0;
}
return 0;
}
/* We managed to get the unique name, then let's use that to name our transient units. */
static int connect_bus(sd_bus **ret) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
id = startswith(unique, ":1."); /* let' strip the usual prefix */
if (!id)
id = startswith(unique, ":"); /* the spec only requires things to start with a colon, hence
* let's add a generic fallback for that. */
if (!id)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unique name %s has unexpected format.",
unique);
assert(ret);
/* The unique D-Bus names are actually unique per D-Bus instance, so on soft-reboot they will wrap
* and start over since the D-Bus broker is restarted. If there's a failed unit left behind that
* hasn't been garbage collected, we'll conflict. Append the soft-reboot counter to avoid clashing. */
if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = bus_get_property_trivial(
bus, bus_systemd_mgr, "SoftRebootsCount", &error, 'u', &soft_reboots_count);
if (r < 0)
log_debug_errno(r,
"Failed to get SoftRebootsCount property, ignoring: %s",
bus_error_message(&error, r));
}
/* If --wait is used connect via the bus, unconditionally, as ref/unref is not supported via the
* limited direct connection */
if (arg_wait ||
arg_stdio != ARG_STDIO_NONE ||
(arg_runtime_scope == RUNTIME_SCOPE_USER && !IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE)))
r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus);
else
r = bus_connect_transport_systemd(arg_transport, arg_host, arg_runtime_scope, &bus);
if (r < 0)
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
if (soft_reboots_count > 0) {
if (asprintf(&p, "run-u%s-s%u.%s", id, soft_reboots_count, unit_type_to_string(t)) < 0)
return log_oom();
} else {
p = strjoin("run-u", id, ".", unit_type_to_string(t));
if (!p)
return log_oom();
}
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
*ret = p;
*ret = TAKE_PTR(bus);
return 0;
}
typedef struct RunContext {
sd_bus *bus;
sd_event *event;
PTYForward *forward;
sd_bus_slot *match;
char *service;
char *bus_path;
/* Bus objects */
sd_bus *bus;
sd_bus_slot *match_properties_changed;
sd_bus_slot *match_disconnected;
sd_event_source *retry_timer;
/* Current state of the unit */
char *active_state;
@ -1437,16 +1491,72 @@ typedef struct RunContext {
uint32_t exit_status;
} RunContext;
static void run_context_free(RunContext *c) {
static int run_context_update(RunContext *c);
static int run_context_attach_bus(RunContext *c, sd_bus *bus);
static void run_context_detach_bus(RunContext *c);
static int run_context_reconnect(RunContext *c);
static void run_context_done(RunContext *c) {
assert(c);
run_context_detach_bus(c);
c->retry_timer = sd_event_source_disable_unref(c->retry_timer);
c->forward = pty_forward_free(c->forward);
c->match = sd_bus_slot_unref(c->match);
c->bus = sd_bus_unref(c->bus);
c->event = sd_event_unref(c->event);
free(c->active_state);
free(c->result);
free(c->bus_path);
free(c->service);
}
static int on_retry_timer(sd_event_source *s, uint64_t usec, void *userdata) {
RunContext *c = ASSERT_PTR(userdata);
c->retry_timer = sd_event_source_disable_unref(c->retry_timer);
return run_context_reconnect(c);
}
static int run_context_reconnect(RunContext *c) {
int r;
assert(c);
run_context_detach_bus(c);
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
r = connect_bus(&bus);
if (r < 0) {
log_warning_errno(r, "Failed to reconnect, retrying in 2s: %m");
r = event_reset_time_relative(
c->event,
&c->retry_timer,
CLOCK_MONOTONIC,
2 * USEC_PER_SEC, /* accuracy= */ 0,
on_retry_timer, c,
SD_EVENT_PRIORITY_NORMAL,
"retry-timeout",
/* force_reset= */ false);
if (r < 0) {
(void) sd_event_exit(c->event, EXIT_FAILURE);
return log_error_errno(r, "Failed to install retry timer: %m");
}
return 0;
}
r = run_context_attach_bus(c, bus);
if (r < 0) {
(void) sd_event_exit(c->event, EXIT_FAILURE);
return r;
}
log_info("Reconnected to bus.");
return run_context_update(c);
}
static void run_context_check_done(RunContext *c) {
@ -1454,16 +1564,13 @@ static void run_context_check_done(RunContext *c) {
assert(c);
if (c->match)
done = STRPTR_IN_SET(c->active_state, "inactive", "failed") && !c->has_job;
else
done = true;
done = STRPTR_IN_SET(c->active_state, "inactive", "failed") && !c->has_job;
if (c->forward && !pty_forward_is_done(c->forward) && done) /* If the service is gone, it's time to drain the output */
done = pty_forward_drain(c->forward);
if (done)
sd_event_exit(c->event, EXIT_SUCCESS);
(void) sd_event_exit(c->event, EXIT_SUCCESS);
}
static int map_job(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
@ -1480,7 +1587,7 @@ static int map_job(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_er
return 0;
}
static int run_context_update(RunContext *c, const char *path) {
static int run_context_update(RunContext *c) {
static const struct bus_properties_map map[] = {
{ "ActiveState", "s", NULL, offsetof(RunContext, active_state) },
@ -1503,16 +1610,35 @@ static int run_context_update(RunContext *c, const char *path) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
r = bus_map_all_properties(c->bus,
"org.freedesktop.systemd1",
path,
map,
BUS_MAP_STRDUP,
&error,
NULL,
c);
assert(c);
assert(c->bus);
r = bus_map_all_properties(
c->bus,
"org.freedesktop.systemd1",
c->bus_path,
map,
BUS_MAP_STRDUP,
&error,
NULL,
c);
if (r < 0) {
sd_event_exit(c->event, EXIT_FAILURE);
/* If this is a connection error, then try to reconnect. This might be because the service
* manager is being restarted. Handle this gracefully. */
if (sd_bus_error_has_names(
&error,
SD_BUS_ERROR_NO_REPLY,
SD_BUS_ERROR_DISCONNECTED,
SD_BUS_ERROR_TIMED_OUT,
SD_BUS_ERROR_SERVICE_UNKNOWN,
SD_BUS_ERROR_NAME_HAS_NO_OWNER)) {
log_info("Bus call failed due to connection problems. Trying to reconnect...");
/* Not propagating error, because we handled it already, by reconnecting. */
return run_context_reconnect(c);
}
(void) sd_event_exit(c->event, EXIT_FAILURE);
return log_error_errno(r, "Failed to query unit state: %s", bus_error_message(&error, r));
}
@ -1521,11 +1647,67 @@ static int run_context_update(RunContext *c, const char *path) {
}
static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
RunContext *c = ASSERT_PTR(userdata);
return run_context_update(ASSERT_PTR(userdata));
}
assert(m);
static int on_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
/* If our connection gets terminated, then try to reconnect. This might be because the service
* manager is being restarted. Handle this gracefully. */
log_info("Got disconnected from bus connection. Trying to reconnect...");
return run_context_reconnect(ASSERT_PTR(userdata));
}
return run_context_update(c, sd_bus_message_get_path(m));
static int run_context_attach_bus(RunContext *c, sd_bus *bus) {
int r;
assert(c);
assert(bus);
assert(!c->bus);
assert(!c->match_properties_changed);
assert(!c->match_disconnected);
c->bus = sd_bus_ref(bus);
r = sd_bus_match_signal_async(
c->bus,
&c->match_properties_changed,
"org.freedesktop.systemd1",
c->bus_path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
on_properties_changed, NULL, c);
if (r < 0)
return log_error_errno(r, "Failed to request PropertiesChanged signal match: %m");
r = sd_bus_match_signal_async(
bus,
&c->match_disconnected,
"org.freedesktop.DBus.Local",
/* path= */ NULL,
"org.freedesktop.DBus.Local",
"Disconnected",
on_disconnected, NULL, c);
if (r < 0)
return log_error_errno(r, "Failed to request Disconnected signal match: %m");
r = sd_bus_attach_event(c->bus, c->event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
return 0;
}
static void run_context_detach_bus(RunContext *c) {
assert(c);
if (c->bus) {
(void) sd_bus_detach_event(c->bus);
c->bus = sd_bus_unref(c->bus);
}
c->match_properties_changed = sd_bus_slot_unref(c->match_properties_changed);
c->match_disconnected = sd_bus_slot_unref(c->match_disconnected);
}
static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) {
@ -1541,7 +1723,7 @@ static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) {
/* If --wait is specified, we'll only exit the pty forwarding, but will continue to wait
* for the service to end. If the user hits ^C we'll exit too. */
} else if (rcode < 0) {
sd_event_exit(c->event, EXIT_FAILURE);
(void) sd_event_exit(c->event, EXIT_FAILURE);
return log_error_errno(rcode, "Error on PTY forwarding logic: %m");
}
@ -1818,7 +2000,7 @@ static int start_transient_service(sd_bus *bus) {
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
} else {
r = make_unit_name(bus, UNIT_SERVICE, &service);
r = make_unit_name(UNIT_SERVICE, &service);
if (r < 0)
return r;
}
@ -1860,7 +2042,7 @@ static int start_transient_service(sd_bus *bus) {
}
if (arg_wait || arg_stdio != ARG_STDIO_NONE) {
_cleanup_(run_context_free) RunContext c = {
_cleanup_(run_context_done) RunContext c = {
.cpu_usage_nsec = NSEC_INFINITY,
.memory_peak = UINT64_MAX,
.memory_swap_peak = UINT64_MAX,
@ -1871,14 +2053,19 @@ static int start_transient_service(sd_bus *bus) {
.inactive_exit_usec = USEC_INFINITY,
.inactive_enter_usec = USEC_INFINITY,
};
_cleanup_free_ char *path = NULL;
c.bus = sd_bus_ref(bus);
r = sd_event_default(&c.event);
if (r < 0)
return log_error_errno(r, "Failed to get event loop: %m");
c.service = strdup(service);
if (!c.service)
return log_oom();
c.bus_path = unit_dbus_path_from_name(service);
if (!c.bus_path)
return log_oom();
if (master >= 0) {
(void) sd_event_set_signal_exit(c.event, true);
@ -1900,26 +2087,11 @@ static int start_transient_service(sd_bus *bus) {
set_window_title(c.forward);
}
path = unit_dbus_path_from_name(service);
if (!path)
return log_oom();
r = sd_bus_match_signal_async(
bus,
&c.match,
"org.freedesktop.systemd1",
path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
on_properties_changed, NULL, &c);
r = run_context_attach_bus(&c, bus);
if (r < 0)
return log_error_errno(r, "Failed to request properties changed signal match: %m");
return r;
r = sd_bus_attach_event(bus, c.event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
r = run_context_update(&c, path);
r = run_context_update(&c);
if (r < 0)
return r;
@ -2033,7 +2205,7 @@ static int start_transient_scope(sd_bus *bus) {
if (r < 0)
return log_error_errno(r, "Failed to mangle scope name: %m");
} else {
r = make_unit_name(bus, UNIT_SCOPE, &scope);
r = make_unit_name(UNIT_SCOPE, &scope);
if (r < 0)
return r;
}
@ -2332,7 +2504,7 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
break;
}
} else {
r = make_unit_name(bus, UNIT_SERVICE, &service);
r = make_unit_name(UNIT_SERVICE, &service);
if (r < 0)
return r;
@ -2411,16 +2583,30 @@ static int run(int argc, char* argv[]) {
}
if (!arg_description) {
char *t;
_cleanup_free_ char *t = NULL;
if (strv_isempty(arg_cmdline))
t = strdup(arg_unit);
else
else if (startswith(arg_cmdline[0], "-")) {
/* Drop the login shell marker from the command line when generating the description,
* in order to minimize user confusion. */
_cleanup_strv_free_ char **l = strv_copy(arg_cmdline);
if (!l)
return log_oom();
r = free_and_strdup_warn(l + 0, l[0] + 1);
if (r < 0)
return r;
t = quote_command_line(l, SHELL_ESCAPE_EMPTY);
} else
t = quote_command_line(arg_cmdline, SHELL_ESCAPE_EMPTY);
if (!t)
return log_oom();
free_and_replace(arg_description, t);
arg_description = strjoin("[", program_invocation_short_name, "] ", t);
if (!arg_description)
return log_oom();
}
/* For backward compatibility reasons env var expansion is disabled by default for scopes, and
@ -2435,18 +2621,9 @@ static int run(int argc, char* argv[]) {
" Use --expand-environment=yes/no to explicitly control it as needed.");
}
/* If --wait is used connect via the bus, unconditionally, as ref/unref is not supported via the
* limited direct connection */
if (arg_wait ||
arg_stdio != ARG_STDIO_NONE ||
(arg_runtime_scope == RUNTIME_SCOPE_USER && !IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE)))
r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus);
else
r = bus_connect_transport_systemd(arg_transport, arg_host, arg_runtime_scope, &bus);
r = connect_bus(&bus);
if (r < 0)
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
return r;
if (arg_scope)
return start_transient_scope(bus);

View File

@ -112,6 +112,30 @@ static SD_VARLINK_DEFINE_METHOD(
ResetStatistics,
VARLINK_DEFINE_POLKIT_INPUT);
static SD_VARLINK_DEFINE_STRUCT_TYPE(
DNSServer,
SD_VARLINK_DEFINE_FIELD(address, SD_VARLINK_INT, SD_VARLINK_ARRAY),
SD_VARLINK_DEFINE_FIELD(addressFamily, SD_VARLINK_INT, 0),
SD_VARLINK_DEFINE_FIELD(port, SD_VARLINK_INT, 0),
SD_VARLINK_DEFINE_FIELD(interfaceSpecifier, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_FIELD(serverName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_FIELD(accessible, SD_VARLINK_BOOL, 0));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
DNSConfiguration,
SD_VARLINK_DEFINE_FIELD(interface, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_FIELD(interfaceIndex, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(currentDNSServer, DNSServer, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(dnsServers, DNSServer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_FIELD(searchDomains, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD_FULL(
SubscribeDNSConfiguration,
SD_VARLINK_REQUIRES_MORE,
SD_VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The current global and per-interface DNS configurations"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(configuration, DNSConfiguration, SD_VARLINK_ARRAY));
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_Resolve_Monitor,
"io.systemd.Resolve.Monitor",
@ -129,4 +153,7 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_type_TransactionStatistics,
&vl_type_CacheStatistics,
&vl_type_DnssecStatistics,
&vl_type_ServerState);
&vl_type_ServerState,
&vl_type_DNSServer,
&vl_type_DNSConfiguration,
&vl_method_SubscribeDNSConfiguration);

View File

@ -82,7 +82,7 @@ TEST(keymaps) {
#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x))
TEST(dump_special_glyphs) {
assert_cc(SPECIAL_GLYPH_GREEN_CIRCLE + 1 == _SPECIAL_GLYPH_MAX);
assert_cc(SPECIAL_GLYPH_SUPERHERO + 1 == _SPECIAL_GLYPH_MAX);
log_info("is_locale_utf8: %s", yes_no(is_locale_utf8()));
@ -133,6 +133,7 @@ TEST(dump_special_glyphs) {
dump_glyph(SPECIAL_GLYPH_YELLOW_CIRCLE);
dump_glyph(SPECIAL_GLYPH_BLUE_CIRCLE);
dump_glyph(SPECIAL_GLYPH_GREEN_CIRCLE);
dump_glyph(SPECIAL_GLYPH_SUPERHERO);
}
DEFINE_TEST_MAIN(LOG_INFO);

View File

@ -1067,6 +1067,55 @@ DNS=127.0.0.1
self.show_journal('systemd-timedated.service')
self.fail(f'Timezone: {tz}, expected: Pacific/Honolulu')
def test_wait_online_dns(self):
''' test systemd-networkd-wait-online with --dns '''
self.start_unit('systemd-resolved')
self.write_network(
self.config,
(
'[Match]\n'
f'Name={self.iface}\n'
'[Network]\n'
'DHCP=ipv4\n'
'UseDNS=yes\n'
'DNSDefaultRoute=yes\n'
)
)
self.create_iface()
self.start_unit('systemd-networkd')
subprocess.check_call(
[NETWORKD_WAIT_ONLINE, '--dns', '--interface', self.iface, '--timeout=10']
)
def test_wait_online_dns_expect_timeout(self):
''' test systemd-networkd-wait-online with --dns, and expect timeout '''
self.start_unit('systemd-resolved')
self.write_network(
self.config,
(
'[Match]\n'
f'Name={self.iface}\n'
'[Network]\n'
'DHCP=ipv4\n'
'UseDNS=no\n'
'DNSDefaultRoute=yes\n'
)
)
self.create_iface()
self.start_unit('systemd-networkd')
env = os.environ.copy()
env['SYSTEMD_LOG_LEVEL'] = 'debug'
r = subprocess.run(
[NETWORKD_WAIT_ONLINE, '--dns', '--interface', self.iface, '--timeout=5'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
)
subprocess.check_call(['resolvectl'])
self.assertNotEqual(r.returncode, 0)
self.assertRegex(r.stderr, rb'No.*DNS server is accessible')
class MatchClientTest(unittest.TestCase, NetworkdTestingUtilities):
"""Test [Match] sections in .network files.

View File

@ -261,4 +261,17 @@ if [[ -e /usr/lib/pam.d/systemd-run0 ]] || [[ -e /etc/pam.d/systemd-run0 ]]; the
assert_eq "$(run0 -D / pwd)" "/"
assert_eq "$(run0 --user=testuser pwd)" "/home/testuser"
assert_eq "$(run0 -D / --user=testuser pwd)" "/"
# Verify that all combinations of --pty/--pipe come to the sam results
assert_eq "$(run0 echo -n foo)" "foo"
assert_eq "$(run0 --pty echo -n foo)" "foo"
assert_eq "$(run0 --pipe echo -n foo)" "foo"
assert_eq "$(run0 --pipe --pty echo -n foo)" "foo"
# Validate when we invoke run0 without a tty, that depending on --pty it either allocates a tty or not
assert_neq "$(run0 --pty tty < /dev/null)" "not a tty"
assert_eq "$(run0 --pipe tty < /dev/null)" "not a tty"
fi
# Tests whether intermediate disconnects corrupt us (modified testcase from https://github.com/systemd/systemd/issues/27204)
assert_rc "37" systemd-run --unit=disconnecttest --wait --pipe --user -M testuser@.host bash -ec 'systemctl --user daemon-reexec; sleep 3; exit 37'

View File

@ -934,6 +934,15 @@ testcase_11_nft() {
# Test resolvectl show-server-state
testcase_12_resolvectl2() {
# Cleanup
# shellcheck disable=SC2317
cleanup() {
rm -f /run/systemd/resolved.conf.d/reload.conf
systemctl reload systemd-resolved.service
}
trap cleanup RETURN
run resolvectl show-server-state
grep -qF "10.0.0.1" "$RUN_OUT"
grep -qF "Interface" "$RUN_OUT"
@ -996,6 +1005,63 @@ testcase_12_resolvectl2() {
restart_resolved
}
# Test io.systemd.Resolve.Monitor.SubscribeDNSConfiguration
testcase_13_varlink_subscribe_dns_configuration() {
# Cleanup
# shellcheck disable=SC2317
cleanup() {
rm -f /run/systemd/resolved.conf.d/global-dns.conf
restart_resolved
}
trap cleanup RETURN
local unit
local tmpfile
unit="subscribe-dns-configuration-$(systemd-id128 new -u).service"
tmpfile=$(mktemp)
# Clear global and per-interface DNS before monitoring the configuration change.
mkdir -p /run/systemd/resolved.conf.d/
{
echo "[Resolve]"
echo "DNS="
} > /run/systemd/resolved.conf.d/global-dns.conf
systemctl reload systemd-resolved.service
resolvectl dns dns0 ""
# Start the call to io.systemd.Resolve.Monitor.SubscribeDNSConfiguration
systemd-run -u "$unit" -p "Type=exec" -p "StandardOutput=truncate:$tmpfile" \
varlinkctl call -E /run/systemd/resolve/io.systemd.Resolve.Monitor io.systemd.Resolve.Monitor.SubscribeDNSConfiguration '{}'
# Check that an initial reply was given with the current expected settings.
assert_in \
'{"global":{"servers":[],"domains":[]}}' \
"$(jq -cr --seq '.configuration.[] | select(.interface == null) | {"global": {servers: .dnsServers, domains: .searchDomains}}' "$tmpfile" | tr -d '\030')"
assert_in \
'{"dns0":{"servers":[],"domains":[]}}' \
"$(jq -cr --seq '.configuration.[] | select(.interface == "dns0") | {"dns0": {servers: .dnsServers, domains: .searchDomains}}' "$tmpfile" | tr -d '\030')"
# Test that changes in global DNS configuration are reflected.
mkdir -p /run/systemd/resolved.conf.d/
{
echo "[Resolve]"
echo "DNS=8.8.8.8"
echo "Domains=lan"
} > /run/systemd/resolved.conf.d/global-dns.conf
systemctl reload systemd-resolved.service
assert_in \
'{"global":{"servers":[[8,8,8,8]],"domains":["lan"]}}' \
"$(jq -cr --seq '.configuration.[] | select(.interface == null) | {"global":{servers: [.dnsServers.[] | .address], domains: .searchDomains}}' "$tmpfile" | tr -d '\030')"
# Test that changes to per-interface DNS are reflected.
resolvectl dns dns0 8.8.4.4 1.1.1.1
assert_in \
'{"dns0":{"servers":[[8,8,4,4],[1,1,1,1]],"domains":[""]}}' \
"$(jq -cr --seq '.configuration.[] | select(.interface == "dns0") | {"dns0":{servers: [.dnsServers.[] | .address], domains: .searchDomains}}' "$tmpfile" | tr -d '\030')"
}
# PRE-SETUP
systemctl unmask systemd-resolved.service
systemctl enable --now systemd-resolved.service

View File

@ -39,6 +39,15 @@ assert_eq() {(
fi
)}
assert_neq() {(
set +ex
if [[ "${1?}" = "${2?}" ]]; then
echo "FAIL: not expected: '$2' actual: '$1'" >&2
exit 1
fi
)}
assert_le() {(
set +ex

View File

@ -14,7 +14,7 @@ ConditionCapability=CAP_NET_ADMIN
DefaultDependencies=no
Conflicts=shutdown.target
BindsTo=systemd-networkd.service
After=systemd-networkd.service
After=systemd-networkd.service systemd-resolved.service
Before=network-online.target shutdown.target
[Service]