mirror of
https://github.com/systemd/systemd.git
synced 2025-01-07 21:18:41 +03:00
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.
This commit is contained in:
parent
0a2ec4a0b3
commit
f9764f05e4
@ -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>
|
||||
|
||||
|
@ -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',
|
||||
|
131
src/network/wait-online/dns-configuration.c
Normal file
131
src/network/wait-online/dns-configuration.c
Normal 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;
|
||||
}
|
37
src/network/wait-online/dns-configuration.h
Normal file
37
src/network/wait-online/dns-configuration.h
Normal 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);
|
@ -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);
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "sd-netlink.h"
|
||||
|
||||
#include "dns-configuration.h"
|
||||
#include "log-link.h"
|
||||
#include "network-util.h"
|
||||
|
||||
@ -25,6 +26,7 @@ struct Link {
|
||||
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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user