Compare commits

...

17 Commits

Author SHA1 Message Date
Nick Rosbrook 4988a38067
Merge c15ab51249 into dbbe895807 2024-11-20 12:54:07 -05:00
Daan De Meyer dbbe895807 test-audit-util: Migrate to new assertion macros 2024-11-20 16:48:55 +00:00
Yu Watanabe 52b0351a15
core/exec-invoke: suppress placeholder home only in build_environment() (#35219)
Alternative to https://github.com/systemd/systemd/pull/34789
Closes #34789
2024-11-20 17:34:25 +09:00
Luca Boccassi fe077a1a58 units: add initrd directory to list of conditions for systemd-confext
systemd-sysext has the same check, but it was forgotten for confexts.
Needed to activate confexts from the ESP in the initrd.
2024-11-20 09:12:24 +01:00
Mike Yuan b718b86e1b
core/exec-invoke: suppress placeholder home only in build_environment()
Currently, get_fixed_user() employs USER_CREDS_SUPPRESS_PLACEHOLDER,
meaning home path is set to NULL if it's empty or root. However,
the path is also used for applying WorkingDirectory=~, and we'd
spuriously use the invoking user's home as fallback even if
User= is changed in that case.

Let's instead delegate such suppression to build_environment(),
so that home is proper initialized for usage at other steps.
shell doesn't actually suffer from such problem, but it's changed
too for consistency.

Alternative to #34789
2024-11-19 00:38:18 +01:00
Mike Yuan d911778877
core/exec-invoke: minor cleanup for apply_working_directory() error handling
Assign exit_status at the same site where error log is emitted,
for readability.
2024-11-19 00:38:18 +01:00
Mike Yuan eea9d3eb10
basic/user-util: split out placeholder suppression from USER_CREDS_CLEAN into its own flag
No functional change, preparation for later commits.
2024-11-19 00:38:18 +01:00
Mike Yuan 579ce77ead
basic/user-util: introduce shell_is_placeholder() helper 2024-11-19 00:38:18 +01:00
Nick Rosbrook c15ab51249 networkd-test: add basic tests for systemd-networkd-wait-online --dns 2024-11-18 11:01:59 -05:00
Nick Rosbrook e9d265bc73 test: add test for resolved SubscribeDNSConfiguration API 2024-11-18 11:01:59 -05:00
Nick Rosbrook 739d63811e test: cleanup after testcase_12_resolvectl2 2024-11-15 13:44:29 -05:00
Nick Rosbrook 40cb08f8c1 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 the property DefaultRoute=yes (either by explicit
configuration, or because there are no routing-only domains), or if the
search domain '.' is configured, wait for link-specific DNS to be
configured. Otherwise, global DNS servers may be considered.
2024-11-15 13:44:29 -05:00
Nick Rosbrook 0a87b4aee1 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-11-15 13:44:29 -05:00
Nick Rosbrook 5b4af64599 resolved: add link_get_default_route helper
The dbus property getter for DefaultRoute does not simply check
link->default_route. Instead, if l->default_route is not explicitly
configured, it checks dns_scope_is_default_route(l->unicast_scope).

Add a link_get_default_route() helper with this logic so that it can be
used for consistency.
2024-11-15 13:44:29 -05:00
Nick Rosbrook 95911ee908 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-11-15 13:44:29 -05:00
Nick Rosbrook ad68334774 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-11-15 13:44:29 -05:00
Nick Rosbrook 133314b44e 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-11-15 13:44:29 -05:00
33 changed files with 1127 additions and 90 deletions

View File

@ -144,6 +144,22 @@
<xi:include href="version-info.xml" xpointer="v249"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--dns</option></term>
<listitem><para>Waiting for DNS servers to be accessible on each interface. 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 a link has the property <varname>DefaultRoute=yes</varname> (either because the
<varname>DNSDefaultRoute=</varname> network property is explicitly configured, or
because the link does not have any "routing-only" domains), or if the search domain "." is
configured, then wait for link-specific DNS servers to be accessible. Otherwise, allow global
DNS servers to satisfy the condition.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--any</option></term>

View File

@ -220,9 +220,9 @@ static int synthesize_user_creds(
if (ret_gid)
*ret_gid = GID_NOBODY;
if (ret_home)
*ret_home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/";
*ret_home = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/";
if (ret_shell)
*ret_shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN;
*ret_shell = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN;
return 0;
}
@ -244,6 +244,7 @@ int get_user_creds(
assert(username);
assert(*username);
assert((ret_home || ret_shell) || !(flags & (USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_CLEAN)));
if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
(!ret_home && !ret_shell)) {
@ -315,17 +316,14 @@ int get_user_creds(
if (ret_home)
/* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
*ret_home = (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
(empty_or_root(p->pw_dir) ||
!path_is_valid(p->pw_dir) ||
!path_is_absolute(p->pw_dir))) ? NULL : p->pw_dir;
*ret_home = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(p->pw_dir)) ||
(FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_dir) || !path_is_absolute(p->pw_dir)))
? NULL : p->pw_dir;
if (ret_shell)
*ret_shell = (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
(isempty(p->pw_shell) ||
!path_is_valid(p->pw_shell) ||
!path_is_absolute(p->pw_shell) ||
is_nologin_shell(p->pw_shell))) ? NULL : p->pw_shell;
*ret_shell = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(p->pw_shell)) ||
(FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_shell) || !path_is_absolute(p->pw_shell)))
? NULL : p->pw_shell;
if (patch_username)
*username = p->pw_name;

View File

@ -12,6 +12,8 @@
#include <sys/types.h>
#include <unistd.h>
#include "string-util.h"
/* Users managed by systemd-homed. See https://systemd.io/UIDS-GIDS for details how this range fits into the rest of the world */
#define HOME_UID_MIN ((uid_t) 60001)
#define HOME_UID_MAX ((uid_t) 60513)
@ -36,10 +38,20 @@ static inline int parse_gid(const char *s, gid_t *ret_gid) {
char* getlogname_malloc(void);
char* getusername_malloc(void);
const char* default_root_shell_at(int rfd);
const char* default_root_shell(const char *root);
bool is_nologin_shell(const char *shell);
static inline bool shell_is_placeholder(const char *shell) {
return isempty(shell) || is_nologin_shell(shell);
}
typedef enum UserCredsFlags {
USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */
USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */
USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */
USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */
USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */
USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */
USER_CREDS_SUPPRESS_PLACEHOLDER = 1 << 3, /* suppress home and/or shell fields if value is placeholder (root/empty/nologin) */
} UserCredsFlags;
int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags);
@ -125,10 +137,6 @@ int fgetsgent_sane(FILE *stream, struct sgrp **sg);
int putsgent_sane(const struct sgrp *sg, FILE *stream);
#endif
bool is_nologin_shell(const char *shell);
const char* default_root_shell_at(int rfd);
const char* default_root_shell(const char *root);
int is_this_me(const char *username);
const char* get_home_root(void);

View File

@ -855,9 +855,6 @@ static int get_fixed_user(
assert(user_or_uid);
assert(ret_username);
/* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway
* (i.e. are "/" or "/bin/nologin"). */
r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, USER_CREDS_CLEAN);
if (r < 0)
return r;
@ -1883,7 +1880,10 @@ static int build_environment(
}
}
if (home && set_user_login_env) {
/* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway
* (i.e. are "/" or "/bin/nologin"). */
if (home && set_user_login_env && !empty_or_root(home)) {
x = strjoin("HOME=", home);
if (!x)
return -ENOMEM;
@ -1892,7 +1892,7 @@ static int build_environment(
our_env[n_env++] = x;
}
if (shell && set_user_login_env) {
if (shell && set_user_login_env && !shell_is_placeholder(shell)) {
x = strjoin("SHELL=", shell);
if (!x)
return -ENOMEM;
@ -3471,20 +3471,16 @@ static int apply_working_directory(
const ExecContext *context,
const ExecParameters *params,
ExecRuntime *runtime,
const char *home,
int *exit_status) {
const char *home) {
const char *wd;
int r;
assert(context);
assert(exit_status);
if (context->working_directory_home) {
if (!home) {
*exit_status = EXIT_CHDIR;
if (!home)
return -ENXIO;
}
wd = home;
} else
@ -3503,13 +3499,7 @@ static int apply_working_directory(
if (r >= 0)
r = RET_NERRNO(fchdir(dfd));
}
if (r < 0 && !context->working_directory_missing_ok) {
*exit_status = EXIT_CHDIR;
return r;
}
return 0;
return context->working_directory_missing_ok ? 0 : r;
}
static int apply_root_directory(
@ -3785,7 +3775,7 @@ static int acquire_home(const ExecContext *c, const char **home, char **ret_buf)
if (!c->working_directory_home)
return 0;
if (c->dynamic_user)
if (c->dynamic_user || (c->user && is_this_me(c->user) <= 0))
return -EADDRNOTAVAIL;
r = get_home_dir(ret_buf);
@ -4543,7 +4533,7 @@ int exec_invoke(
r = acquire_home(context, &home, &home_buffer);
if (r < 0) {
*exit_status = EXIT_CHDIR;
return log_exec_error_errno(context, params, r, "Failed to determine $HOME for user: %m");
return log_exec_error_errno(context, params, r, "Failed to determine $HOME for the invoking user: %m");
}
/* If a socket is connected to STDIN/STDOUT/STDERR, we must drop O_NONBLOCK */
@ -5382,9 +5372,11 @@ int exec_invoke(
* running this service might have the correct privilege to change to the working directory. Also, it
* is absolutely 💣 crucial 💣 we applied all mount namespacing rearrangements before this, so that
* the cwd cannot be used to pin directories outside of the sandbox. */
r = apply_working_directory(context, params, runtime, home, exit_status);
if (r < 0)
r = apply_working_directory(context, params, runtime, home);
if (r < 0) {
*exit_status = EXIT_CHDIR;
return log_exec_error_errno(context, params, r, "Changing to the requested working directory failed: %m");
}
if (needs_sandboxing) {
/* Apply other MAC contexts late, but before seccomp syscall filtering, as those should really be last to

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

@ -114,6 +114,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,244 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "alloc-util.h"
#include "dns-configuration.h"
#include "iovec-util.h"
#include "hash-funcs.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);
}
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
dns_server_hash_ops,
void,
trivial_hash_func,
trivial_compare_func,
DNSServer,
dns_server_free);
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, NULL, 0, SD_JSON_MANDATORY },
{ "family", 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 },
{ "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSServer, ifindex), SD_JSON_RELAX },
{ "name", 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;
_cleanup_(iovec_done) struct iovec iov = {};
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;
/* We ignored the address field above. Handle it now. */
r = json_dispatch_byte_array_iovec("address", sd_json_variant_by_key(variant, "address"), flags, &iov);
if (r < 0)
return r;
if (iov.iov_len != FAMILY_ADDRESS_SIZE_SAFE(s->family))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field 'address' is array of unexpected size.");
memcpy(&s->addr, iov.iov_base, iov.iov_len);
*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;
sd_json_variant *v = NULL;
int r;
dns_servers = set_new(&dns_server_hash_ops);
if (!dns_servers)
return log_oom();
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;
}
SearchDomain *search_domain_free(SearchDomain *d) {
if (!d)
return NULL;
free(d->name);
return mfree(d);
}
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
search_domain_hash_ops,
void,
trivial_hash_func,
trivial_compare_func,
SearchDomain,
search_domain_free);
static int dispatch_search_domain(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
static const sd_json_dispatch_field search_domain_dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(SearchDomain, name), SD_JSON_MANDATORY },
{ "routeOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(SearchDomain, route_only), SD_JSON_MANDATORY },
{ "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(SearchDomain, ifindex), SD_JSON_RELAX },
{},
};
SearchDomain **ret = ASSERT_PTR(userdata);
_cleanup_(search_domain_freep) SearchDomain *d = NULL;
int r;
d = new0(SearchDomain, 1);
if (!d)
return log_oom();
r = sd_json_dispatch(variant, search_domain_dispatch_table, flags, d);
if (r < 0)
return r;
*ret = TAKE_PTR(d);
return 0;
}
static int dispatch_search_domain_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
Set **ret = ASSERT_PTR(userdata);
Set *search_domains;
sd_json_variant *v = NULL;
int r;
search_domains = set_new(&search_domain_hash_ops);
if (!search_domains)
return log_oom();
JSON_VARIANT_ARRAY_FOREACH(v, variant) {
_cleanup_(search_domain_freep) SearchDomain *d = NULL;
d = new0(SearchDomain, 1);
if (!d)
return log_oom();
r = dispatch_search_domain(name, v, flags, &d);
if (r < 0)
return json_log(v, flags, r, "JSON array element is not a valid SearchDomain.");
r = set_put(search_domains, TAKE_PTR(d));
if (r < 0)
return log_oom();
}
set_free_and_replace(*ret, search_domains);
return 0;
}
DNSConfiguration *dns_configuration_free(DNSConfiguration *c) {
if (!c)
return NULL;
dns_server_free(c->current_dns_server);
set_free(c->dns_servers);
set_free(c->search_domains);
free(c->ifname);
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[] = {
{ "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSConfiguration, ifname), 0 },
{ "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSConfiguration, ifindex), SD_JSON_RELAX },
{ "defaultRoute", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DNSConfiguration, default_route), 0 },
{ "currentServer", SD_JSON_VARIANT_OBJECT, dispatch_dns_server, offsetof(DNSConfiguration, current_dns_server), 0 },
{ "servers", SD_JSON_VARIANT_ARRAY, dispatch_dns_server_array, offsetof(DNSConfiguration, dns_servers), 0 },
{ "searchDomains", SD_JSON_VARIANT_ARRAY, dispatch_search_domain_array, 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;
}
bool dns_configuration_contains_search_domain(DNSConfiguration *c, const char *domain) {
SearchDomain *d = NULL;
assert(domain);
if (!c)
return false;
SET_FOREACH(d, c->search_domains)
if (streq(d->name, domain))
return true;
return false;
}

View File

@ -0,0 +1,49 @@
/* 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 SearchDomain SearchDomain;
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 SearchDomain {
char *name;
bool route_only;
int ifindex;
};
SearchDomain *search_domain_free(SearchDomain *d);
DEFINE_TRIVIAL_CLEANUP_FUNC(SearchDomain*, search_domain_free);
struct DNSConfiguration {
char *ifname;
int ifindex;
bool default_route;
DNSServer *current_dns_server;
Set *dns_servers;
Set *search_domains;
};
int dns_configuration_from_json(sd_json_variant *variant, DNSConfiguration **ret);
bool dns_is_accessible(DNSConfiguration *c);
bool dns_configuration_contains_search_domain(DNSConfiguration *c, const char *domain);
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);

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,7 @@ struct Link {
LinkAddressState ipv4_address_state;
LinkAddressState ipv6_address_state;
char *state;
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,26 @@ static int manager_link_is_online(Manager *m, Link *l, const LinkOperationalStat
"No routable IPv6 address is configured.");
}
if (m->requires_dns) {
if (!l->dns_configuration)
return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL),
"No DNS configuration yet");
/* If a link is configured with DNSDefaultRoute=yes, or is configured with the
* search domain '.', then require link-specific DNS servers to be available.
* Otherwise, we check the global DNS configuration. */
if (l->dns_configuration->default_route ||
dns_configuration_contains_search_domain(l->dns_configuration, ".")) {
if (!dns_is_accessible(l->dns_configuration))
return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL),
"No link-specific DNS server is accessible.");
} else 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 +407,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 (!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 +519,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 +544,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 +565,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

@ -10,6 +10,7 @@
#include "daemon-util.h"
#include "main-func.h"
#include "manager.h"
#include "parse-argument.h"
#include "pretty-print.h"
#include "signal-util.h"
#include "socket-util.h"
@ -22,6 +23,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 +50,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 +109,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_IGNORE,
ARG_ANY,
ARG_TIMEOUT,
ARG_DNS,
};
static const struct option options[] = {
@ -119,6 +123,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", optional_argument, NULL, ARG_DNS },
{}
};
@ -178,6 +183,12 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_DNS:
r = parse_boolean_argument("--dns", optarg, &arg_requires_dns);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
@ -204,7 +215,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

@ -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,5 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "alloc-util.h"
#include "dns-domain.h"
#include "resolved-dns-search-domain.h"
@ -197,3 +199,21 @@ int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDo
*ret = NULL;
return 0;
}
int dns_search_domain_dump_to_json(DnsSearchDomain *domain, sd_json_variant **ret) {
int ifindex = 0;
assert(domain);
assert(ret);
if (domain->type == DNS_SEARCH_DOMAIN_LINK) {
assert(domain->link);
ifindex = domain->link->ifindex;
}
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_STRING("name", domain->name),
SD_JSON_BUILD_PAIR_BOOLEAN("routeOnly", domain->route_only),
SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", SD_JSON_BUILD_UNSIGNED(ifindex)));
}

View File

@ -1,6 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-json.h"
#include "list.h"
#include "macro.h"
@ -54,3 +56,5 @@ static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) {
}
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref);
int dns_search_domain_dump_to_json(DnsSearchDomain *domain, sd_json_variant **ret);

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 *s) {
_cleanup_close_ int fd = -EBADF;
union sockaddr_union sa;
int ifindex, r;
assert(s);
if (s->accessible >= 0)
return s->accessible;
r = sockaddr_set_in_addr(&sa, s->family, &s->address, dns_server_port(s));
if (r < 0)
return r;
fd = socket(s->family, SOCK_DGRAM|SOCK_CLOEXEC, 0);
if (fd < 0)
return -errno;
ifindex = dns_server_ifindex(s);
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 (s->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("family", server->family),
SD_JSON_BUILD_PAIR_UNSIGNED("port", dns_server_port(server)),
SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", SD_JSON_BUILD_UNSIGNED(ifindex)),
JSON_BUILD_PAIR_STRING_NON_EMPTY("name", 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,9 @@ 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 *s);
static inline void dns_server_reset_accessible(DnsServer *s) {
s->accessible = -1;
}

View File

@ -24,6 +24,7 @@ static BUS_DEFINE_PROPERTY_GET(property_get_dnssec_supported, "b", Link, link_dn
static BUS_DEFINE_PROPERTY_GET2(property_get_dnssec_mode, "s", Link, link_get_dnssec_mode, dnssec_mode_to_string);
static BUS_DEFINE_PROPERTY_GET2(property_get_llmnr_support, "s", Link, link_get_llmnr_support, resolve_support_to_string);
static BUS_DEFINE_PROPERTY_GET2(property_get_mdns_support, "s", Link, link_get_mdns_support, resolve_support_to_string);
static BUS_DEFINE_PROPERTY_GET(property_get_default_route, "b", Link, link_get_default_route);
static int property_get_dns_over_tls_mode(
sd_bus *bus,
@ -160,30 +161,6 @@ static int property_get_domains(
return sd_bus_message_close_container(reply);
}
static int property_get_default_route(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
Link *l = ASSERT_PTR(userdata);
assert(reply);
/* Return what is configured, if there's something configured */
if (l->default_route >= 0)
return sd_bus_message_append(reply, "b", l->default_route);
/* Otherwise report what is in effect */
if (l->unicast_scope)
return sd_bus_message_append(reply, "b", dns_scope_is_default_route(l->unicast_scope));
return sd_bus_message_append(reply, "b", false);
}
static int property_get_scopes_mask(
sd_bus *bus,
const char *path,
@ -293,6 +270,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 +746,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

@ -14,6 +14,7 @@
#include "mkdir.h"
#include "netif-util.h"
#include "parse-util.h"
#include "resolved-dns-scope.h"
#include "resolved-link.h"
#include "resolved-llmnr.h"
#include "resolved-mdns.h"
@ -299,6 +300,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. */
dns_server_reset_accessible(s);
return 0;
clear:
@ -831,6 +838,20 @@ ResolveSupport link_get_mdns_support(Link *link) {
return MIN(link->mdns_support, link->manager->mdns_support);
}
bool link_get_default_route(Link *l) {
assert(l);
/* Return what is configured, if there's something configured */
if (l->default_route >= 0)
return l->default_route;
/* Otherwise report what is in effect */
if (l->unicast_scope)
return dns_scope_is_default_route(l->unicast_scope);
return false;
}
int link_address_new(Link *l,
LinkAddress **ret,
int family,

View File

@ -109,6 +109,8 @@ DnsOverTlsMode link_get_dns_over_tls_mode(Link *l);
ResolveSupport link_get_llmnr_support(Link *link);
ResolveSupport link_get_mdns_support(Link *link);
bool link_get_default_route(Link *l);
int link_save_user(Link *l);
int link_load_user(Link *l);
void link_remove_user(Link *l);

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,124 @@ 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,
int default_route,
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) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
r = dns_search_domain_dump_to_json(d, &v);
if (r < 0)
return r;
r = sd_json_variant_append_array(&search_domains_json, v);
if (r < 0)
return r;
}
return sd_json_variant_append_arraybo(
configuration,
JSON_BUILD_PAIR_STRING_NON_EMPTY("ifname", ifname),
SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", SD_JSON_BUILD_UNSIGNED(ifindex)),
SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "defaultRoute", SD_JSON_BUILD_BOOLEAN(default_route > 0)),
JSON_BUILD_PAIR_VARIANT_NON_NULL("currentServer", current_dns_server_json),
SD_JSON_BUILD_PAIR_VARIANT("servers", 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,
/* default_route = */ 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_default_route(l),
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));
}
}
@ -1227,12 +1234,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;
}
@ -1352,6 +1359,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;
@ -1377,7 +1416,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

@ -2297,7 +2297,8 @@ static int start_transient_scope(sd_bus *bus) {
uid_t uid;
gid_t gid;
r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell, USER_CREDS_CLEAN|USER_CREDS_PREFER_NSS);
r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell,
USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS);
if (r < 0)
return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user);

View File

@ -112,6 +112,52 @@ static SD_VARLINK_DEFINE_METHOD(
ResetStatistics,
VARLINK_DEFINE_POLKIT_INPUT);
static SD_VARLINK_DEFINE_STRUCT_TYPE(
DNSServer,
SD_VARLINK_FIELD_COMMENT("IPv4 or IPv6 address of the server."),
SD_VARLINK_DEFINE_FIELD(address, SD_VARLINK_INT, SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Address family of the server, one of AF_INET or AF_INET6."),
SD_VARLINK_DEFINE_FIELD(family, SD_VARLINK_INT, 0),
SD_VARLINK_FIELD_COMMENT("Port number of the server."),
SD_VARLINK_DEFINE_FIELD(port, SD_VARLINK_INT, 0),
SD_VARLINK_FIELD_COMMENT("Interface index for which this server is configured."),
SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Server Name Indication (SNI) of the server."),
SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Indicates if the DNS server is accessible or not."),
SD_VARLINK_DEFINE_FIELD(accessible, SD_VARLINK_BOOL, 0));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
SearchDomain,
SD_VARLINK_FIELD_COMMENT("Domain name."),
SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("Indicates whether or not this is a routing-only domain."),
SD_VARLINK_DEFINE_FIELD(routeOnly, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Interface index for which this search domain is configured."),
SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
DNSConfiguration,
SD_VARLINK_FIELD_COMMENT("Interface name, if any, associated with this configuration. Empty for global configuration."),
SD_VARLINK_DEFINE_FIELD(ifname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Interface index, if any, associated with this configuration. Empty for global configuration."),
SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Indicates whether or not this link's DNS servers will be used for resolving domain names that do not match any link's configured domains."),
SD_VARLINK_DEFINE_FIELD(defaultRoute, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("DNS server currently selected to use for lookups."),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(currentServer, DNSServer, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Array of configured DNS servers."),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(servers, DNSServer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Array of configured search domains."),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(searchDomains, SearchDomain, 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 +175,12 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_type_TransactionStatistics,
&vl_type_CacheStatistics,
&vl_type_DnssecStatistics,
&vl_type_ServerState);
&vl_type_ServerState,
SD_VARLINK_SYMBOL_COMMENT("Encapsulates a DNS server address specification."),
&vl_type_DNSServer,
SD_VARLINK_SYMBOL_COMMENT("Encapsulates a search domain specification."),
&vl_type_SearchDomain,
SD_VARLINK_SYMBOL_COMMENT("Encapsulates a global or per-link DNS configuration, including configured DNS servers, search domains, and more."),
&vl_type_DNSConfiguration,
SD_VARLINK_SYMBOL_COMMENT("Sends the complete global and per-link DNS configurations when any changes are made to them. The current configurations are given immediately when this method is invoked."),
&vl_method_SubscribeDNSConfiguration);

View File

@ -7,24 +7,26 @@ TEST(audit_loginuid_from_pid) {
_cleanup_(pidref_done) PidRef self = PIDREF_NULL, pid1 = PIDREF_NULL;
int r;
assert_se(pidref_set_self(&self) >= 0);
assert_se(pidref_set_pid(&pid1, 1) >= 0);
ASSERT_OK(pidref_set_self(&self));
ASSERT_OK(pidref_set_pid(&pid1, 1));
uid_t uid;
r = audit_loginuid_from_pid(&self, &uid);
assert_se(r >= 0 || r == -ENODATA);
if (r != -ENODATA)
ASSERT_OK(r);
if (r >= 0)
log_info("self audit login uid: " UID_FMT, uid);
assert_se(audit_loginuid_from_pid(&pid1, &uid) == -ENODATA);
ASSERT_ERROR(audit_loginuid_from_pid(&pid1, &uid), ENODATA);
uint32_t sessionid;
r = audit_session_from_pid(&self, &sessionid);
assert_se(r >= 0 || r == -ENODATA);
if (r != -ENODATA)
ASSERT_OK(r);
if (r >= 0)
log_info("self audit session id: %" PRIu32, sessionid);
assert_se(audit_session_from_pid(&pid1, &sessionid) == -ENODATA);
ASSERT_ERROR(audit_session_from_pid(&pid1, &sessionid), ENODATA);
}
static int intro(void) {

View File

@ -1086,6 +1086,139 @@ DNS=127.0.0.1
self.show_journal('systemd-timedated.service')
self.fail(f'Timezone: {tz}, expected: Pacific/Honolulu')
def do_test_wait_online_dns(
self,
iface,
global_dns='',
fallback_dns='',
expect_timeout=False
):
if expect_timeout:
timeout = 5
else:
timeout = 10
# Unless given, clear global DNS configuration
conf = '/run/systemd/resolved.conf.d/global-dns.conf'
os.makedirs(os.path.dirname(conf), exist_ok=True)
with open(conf, 'w') as f:
f.write((
'[Resolve]\n'
f'DNS={global_dns}\n'
f'FallbackDNS={fallback_dns}\n'
))
self.addCleanup(os.remove, conf)
subprocess.check_call(
['systemctl', 'reload', 'systemd-resolved']
)
self.start_unit('systemd-networkd')
env = os.environ.copy()
env['SYSTEMD_LOG_LEVEL'] = 'debug'
env['SYSTEMD_LOG_TARGET'] = 'console'
r = subprocess.run(
[NETWORKD_WAIT_ONLINE, '--dns', '--interface', iface, f'--timeout={timeout}'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
)
if expect_timeout:
self.assertNotEqual(
r.returncode,
0,
f'Command succeeded unexpectedly:\n{r.stderr.decode()}'
)
self.assertRegex(
r.stderr.decode(),
rf'{iface}: No link-specific DNS server is accessible',
f'Missing expected log message:\n{r.stderr.decode()}'
)
else:
self.assertEqual(
r.returncode,
0,
f'Command timed out:\n{r.stderr.decode()}'
)
def test_wait_online_dns(self):
''' test systemd-networkd-wait-online with --dns '''
self.write_network(
self.config,
(
'[Match]\n'
f'Name={self.iface}\n'
'[Network]\n'
'DHCP=ipv4\n'
)
)
self.create_iface()
self.do_test_wait_online_dns(self.iface)
def test_wait_online_dns_global(self):
'''
test systemd-networkd-wait-online with --dns, expect pass due to global DNS
'''
# Explicitly set DNSDefaultRoute=no, and allow global DNS to be used.
self.write_network(
self.config,
(
'[Match]\n'
f'Name={self.iface}\n'
'[Network]\n'
'DHCP=ipv4\n'
'DNSDefaultRoute=no\n'
'[DHCPv4]\n'
'UseDNS=no\n'
)
)
self.create_iface()
self.do_test_wait_online_dns(self.iface, global_dns='192.168.5.1')
def test_wait_online_dns_expect_timeout(self):
''' test systemd-networkd-wait-online with --dns, and expect timeout '''
# Explicitly set DNSDefaultRoute=yes, and require link-specific DNS to be used.
self.write_network(
self.config,
(
'[Match]\n'
f'Name={self.iface}\n'
'[Network]\n'
'DHCP=ipv4\n'
'DNSDefaultRoute=yes\n'
'[DHCPv4]\n'
'UseDNS=no\n'
)
)
self.create_iface()
self.do_test_wait_online_dns(self.iface, expect_timeout=True)
def test_wait_online_dns_expect_timeout_global(self):
'''
test systemd-networkd-wait-online with --dns, and expect timeout
despite global DNS
'''
# Configure Domains=~., and expect timeout despite global DNS servers
# being available.
self.write_network(
self.config,
(
'[Match]\n'
f'Name={self.iface}\n'
'[Network]\n'
'DHCP=ipv4\n'
'Domains=~.\n'
'[DHCPv4]\n'
'UseDNS=no\n'
)
)
self.create_iface()
self.do_test_wait_online_dns(self.iface, expect_timeout=True, global_dns='192.168.5.1')
class MatchClientTest(unittest.TestCase, NetworkdTestingUtilities):
"""Test [Match] sections in .network files.

View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
(! systemd-run --wait -p DynamicUser=yes \
-p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \
-p WorkingDirectory='~' true)
assert_eq "$(systemd-run --pipe --uid=root -p WorkingDirectory='~' pwd)" "/root"
assert_eq "$(systemd-run --pipe --uid=nobody -p WorkingDirectory='~' pwd)" "/"
assert_eq "$(systemd-run --pipe --uid=testuser -p WorkingDirectory='~' pwd)" "/home/testuser"
(! systemd-run --wait -p DynamicUser=yes -p User=testuser \
-p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \
-p WorkingDirectory='~' true)

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,78 @@ testcase_12_resolvectl2() {
restart_resolved
}
# Test io.systemd.Resolve.Monitor.SubscribeDNSConfiguration
testcase_13_varlink_subscribe_dns_configuration() {
# Cleanup
# shellcheck disable=SC2317
cleanup() {
echo "===== io.systemd.Resolve.Monitor.SubscribeDNSConfiguration output: ====="
cat "$tmpfile"
echo "=========="
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 --more --timeout=5 --graceful=io.systemd.TimedOut /run/systemd/resolve/io.systemd.Resolve.Monitor io.systemd.Resolve.Monitor.SubscribeDNSConfiguration '{}'
# Update the global configuration.
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
# Update a link configuration.
resolvectl dns dns0 8.8.4.4 1.1.1.1
# Wait for the monitor to exit gracefully.
while systemctl --quiet is-active "$unit"; do
sleep 0.5
done
# Hack to remove the "Method call returned expected error" line from the output.
sed -i '/^Method call.*returned expected error/d' "$tmpfile"
# Check that an initial reply was given with the settings applied BEFORE the monitor started.
grep -qF \
'{"global":{"servers":[],"domains":[]}}' \
<(jq -cr --seq '.configuration[] | select(.ifname == null) | {"global": {servers: .servers, domains: .searchDomains}}' "$tmpfile")
grep -qF \
'{"dns0":{"servers":[],"domains":[]}}' \
<(jq -cr --seq '.configuration[] | select(.ifname == "dns0") | {"dns0": {servers: .servers, domains: .searchDomains}}' "$tmpfile")
# Check that the global configuration change was reflected.
grep -qF \
'{"global":{"servers":[[8,8,8,8]],"domains":["lan"]}}' \
<(jq -cr --seq '.configuration[] | select(.ifname == null) | {"global":{servers: [.servers[] | .address], domains: [.searchDomains[] | .name]}}' "$tmpfile")
# Check that the link configuration change was reflected.
grep -qF \
'{"dns0":{"servers":[[8,8,4,4],[1,1,1,1]],"domains":[]}}' \
<(jq -cr --seq '.configuration[] | select(.ifname == "dns0") | {"dns0":{servers: [.servers[] | .address], domains: [.searchDomains[] | .name]}}' "$tmpfile")
}
# PRE-SETUP
systemctl unmask systemd-resolved.service
systemctl enable --now systemd-resolved.service

View File

@ -16,6 +16,7 @@ ConditionDirectoryNotEmpty=|/run/confexts
ConditionDirectoryNotEmpty=|/var/lib/confexts
ConditionDirectoryNotEmpty=|/usr/local/lib/confexts
ConditionDirectoryNotEmpty=|/usr/lib/confexts
ConditionDirectoryNotEmpty=|/.extra/confext
DefaultDependencies=no
After=local-fs.target

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]