resolve: extend systemd-resolve so that it can push per-interface DNS configuration into systemd-resolved (#7576)

This is useful to debug things, but also to hook up external post-up
scripts with resolved.

Eventually this code might be useful to implement a
resolvconf(8)-compatible interface for compatibility purposes. Since the
semantics don't map entirely cleanly as first step we add a native
interface for pushing DNS configuration into resolved, that exposes the
correct semantics, before adding any compatibility interface.

See: #7202
This commit is contained in:
Lennart Poettering 2017-12-14 20:13:14 +01:00 committed by GitHub
parent fbd0b64f44
commit 14965b94f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 425 additions and 2 deletions

View File

@ -321,6 +321,48 @@
<listitem><para>Shows the global and per-link DNS settings in currently in effect.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--set-dns=SERVER</option></term>
<term><option>--set-domain=DOMAIN</option></term>
<term><option>--set-llmnr=MODE</option></term>
<term><option>--set-mdns=MODE</option></term>
<term><option>--set-dnssec=MODE</option></term>
<term><option>--set-nta=DOMAIN</option></term>
<listitem><para>Set per-interface DNS configuration. These switches may be used to configure various DNS
settings for network interfaces that aren't managed by
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. (These
commands will fail when used on interfaces that are managed by <command>systemd-networkd</command>, please
configure their DNS settings directly inside the <filename>.network</filename> files instead.) These switches
may be used to inform <command>systemd-resolved</command> about per-interface DNS configuration determined
through external means. Multiple of these switches may be passed on a single invocation of
<command>systemd-resolve</command> in order to set multiple configuration options at once. If any of these
switches is used, it must be combined with <option>--interface=</option> to indicate the network interface the
new DNS configuration belongs to. The <option>--set-dns=</option> option expects an IPv4 or IPv6 address
specification of a DNS server to use, and may be used multiple times to define multiple servers for the same
interface. The <option>--set-domain=</option> option expects a valid DNS domain, possibly prefixed with
<literal>~</literal>, and configures a per-interface search or route-only domain. It may be used multiple times
to configure multiple such domains. The <option>--set-llmnr=</option>, <option>--set-mdns=</option> and
<option>--set-dnssec=</option> options may be used to configure the per-interface LLMNR, MulticastDNS and
DNSSEC settings. Finally, <option>--set-nta=</option> may be used to configure additional per-interface DNSSEC
NTA domains and may also be used multiple times. For details about these settings, their possible values and
their effect, see the corresponding options in
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--revert</option></term>
<listitem><para>Revert the per-interface DNS configuration. This option must be combined with
<option>--interface=</option> to indicate the network interface the DNS configuration shall be reverted on. If
the DNS configuration is reverted all per-interface DNS setting are reset to their defaults, undoing all
effects of <option>--set-dns=</option>, <option>--set-domain=</option>, <option>--set-llmnr=</option>,
<option>--set-mdns=</option>, <option>--set-dnssec=</option>, <option>--set-nta=</option>. Note that when a
network interface disappears all configuration is lost automatically, an explicit reverting is not necessary in
that case.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
@ -403,8 +445,9 @@ _443._tcp.fedoraproject.org IN TLSA 0 0 1 19400be5b7a31fb733917700789d2f0a2471c0
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
<citerefentry><refentrytitle>systemd.dnssd</refentrytitle><manvolnum>5</manvolnum></citerefentry>
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.dnssd</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -26,8 +26,10 @@
#include "af-list.h"
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-util.h"
#include "dns-domain.h"
#include "escape.h"
#include "gcrypt-util.h"
#include "in-addr-util.h"
@ -75,8 +77,18 @@ static enum {
MODE_FLUSH_CACHES,
MODE_RESET_SERVER_FEATURES,
MODE_STATUS,
MODE_SET_LINK,
MODE_REVERT_LINK,
} arg_mode = MODE_RESOLVE_HOST;
static struct in_addr_data *arg_set_dns = NULL;
static size_t arg_n_set_dns = 0;
static char **arg_set_domain = NULL;
static char *arg_set_llmnr = NULL;
static char *arg_set_mdns = NULL;
static char *arg_set_dnssec = NULL;
static char **arg_set_nta = NULL;
static ServiceFamily service_family_from_string(const char *s) {
if (s == NULL || streq(s, "tcp"))
return SERVICE_FAMILY_TCP;
@ -1545,6 +1557,234 @@ static int status_all(sd_bus *bus) {
return r;
}
static int set_link(sd_bus *bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r = 0, q;
assert(bus);
if (arg_n_set_dns > 0) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL;
size_t i;
q = sd_bus_message_new_method_call(
bus,
&req,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"SetLinkDNS");
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_message_append(req, "i", arg_ifindex);
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_message_open_container(req, 'a', "(iay)");
if (q < 0)
return bus_log_create_error(q);
for (i = 0; i < arg_n_set_dns; i++) {
q = sd_bus_message_open_container(req, 'r', "iay");
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_message_append(req, "i", arg_set_dns[i].family);
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_message_append_array(req, 'y', &arg_set_dns[i].address, FAMILY_ADDRESS_SIZE(arg_set_dns[i].family));
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_message_close_container(req);
if (q < 0)
return bus_log_create_error(q);
}
q = sd_bus_message_close_container(req);
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_call(bus, req, 0, &error, NULL);
if (q < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY))
goto is_managed;
log_error_errno(q, "Failed to set DNS configuration: %s", bus_error_message(&error, q));
if (r == 0)
r = q;
}
}
if (!strv_isempty(arg_set_domain)) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL;
char **p;
q = sd_bus_message_new_method_call(
bus,
&req,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"SetLinkDomains");
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_message_append(req, "i", arg_ifindex);
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_message_open_container(req, 'a', "(sb)");
if (q < 0)
return bus_log_create_error(q);
STRV_FOREACH(p, arg_set_domain) {
const char *n;
n = **p == '~' ? *p + 1 : *p;
q = sd_bus_message_append(req, "(sb)", n, **p == '~');
if (q < 0)
return bus_log_create_error(q);
}
q = sd_bus_message_close_container(req);
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_call(bus, req, 0, &error, NULL);
if (q < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY))
goto is_managed;
log_error_errno(q, "Failed to set domain configuration: %s", bus_error_message(&error, q));
if (r == 0)
r = q;
}
}
if (arg_set_llmnr) {
q = sd_bus_call_method(bus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"SetLinkLLMNR",
&error,
NULL,
"is", arg_ifindex, arg_set_llmnr);
if (q < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY))
goto is_managed;
log_error_errno(q, "Failed to set LLMNR configuration: %s", bus_error_message(&error, q));
if (r == 0)
r = q;
}
}
if (arg_set_mdns) {
q = sd_bus_call_method(bus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"SetLinkMulticastDNS",
&error,
NULL,
"is", arg_ifindex, arg_set_mdns);
if (q < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY))
goto is_managed;
log_error_errno(q, "Failed to set MulticastDNS configuration: %s", bus_error_message(&error, q));
if (r == 0)
r = q;
}
}
if (arg_set_dnssec) {
q = sd_bus_call_method(bus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"SetLinkDNSSEC",
&error,
NULL,
"is", arg_ifindex, arg_set_dnssec);
if (q < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY))
goto is_managed;
log_error_errno(q, "Failed to set DNSSEC configuration: %s", bus_error_message(&error, q));
if (r == 0)
r = q;
}
}
if (!strv_isempty(arg_set_nta)) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL;
q = sd_bus_message_new_method_call(
bus,
&req,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"SetLinkDNSSECNegativeTrustAnchors");
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_message_append(req, "i", arg_ifindex);
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_message_append_strv(req, arg_set_nta);
if (q < 0)
return bus_log_create_error(q);
q = sd_bus_call(bus, req, 0, &error, NULL);
if (q < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY))
goto is_managed;
log_error_errno(q, "Failed to set DNSSEC NTA configuration: %s", bus_error_message(&error, q));
if (r == 0)
r = q;
}
}
return r;
is_managed:
{
char ifname[IFNAMSIZ];
return log_error_errno(q,
"The specified interface %s is managed by systemd-networkd. Operation refused.\n"
"Please configure DNS settings for systemd-networkd managed interfaces directly in their .network files.", strna(if_indextoname(arg_ifindex, ifname)));
}
}
static int revert_link(sd_bus *bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(bus);
r = sd_bus_call_method(bus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"RevertLink",
&error,
NULL,
"i", arg_ifindex);
if (r < 0)
return log_error_errno(r, "Failed to revert interface configuration: %s", bus_error_message(&error, r));
return 0;
}
static void help_protocol_types(void) {
if (arg_legend)
puts("Known protocol types:");
@ -1610,6 +1850,13 @@ static void help(void) {
" --flush-caches Flush all local DNS caches\n"
" --reset-server-features\n"
" Forget learnt DNS server feature levels\n"
" --set-dns=SERVER Set per-interface DNS server address\n"
" --set-domain=DOMAIN Set per-interface search domain\n"
" --set-llmnr=MODE Set per-interface LLMNR mode\n"
" --set-mdns=MODE Set per-interface MulticastDNS mode\n"
" --set-dnssec=MODE Set per-interface DNSSEC mode\n"
" --set-nta=DOMAIN Set per-interface DNSSEC NTA\n"
" --revert Revert per-interface configuration\n"
, program_invocation_short_name);
}
@ -1631,6 +1878,13 @@ static int parse_argv(int argc, char *argv[]) {
ARG_FLUSH_CACHES,
ARG_RESET_SERVER_FEATURES,
ARG_NO_PAGER,
ARG_SET_DNS,
ARG_SET_DOMAIN,
ARG_SET_LLMNR,
ARG_SET_MDNS,
ARG_SET_DNSSEC,
ARG_SET_NTA,
ARG_REVERT_LINK,
};
static const struct option options[] = {
@ -1655,6 +1909,13 @@ static int parse_argv(int argc, char *argv[]) {
{ "flush-caches", no_argument, NULL, ARG_FLUSH_CACHES },
{ "reset-server-features", no_argument, NULL, ARG_RESET_SERVER_FEATURES },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "set-dns", required_argument, NULL, ARG_SET_DNS },
{ "set-domain", required_argument, NULL, ARG_SET_DOMAIN },
{ "set-llmnr", required_argument, NULL, ARG_SET_LLMNR },
{ "set-mdns", required_argument, NULL, ARG_SET_MDNS },
{ "set-dnssec", required_argument, NULL, ARG_SET_DNSSEC },
{ "set-nta", required_argument, NULL, ARG_SET_NTA },
{ "revert", no_argument, NULL, ARG_REVERT_LINK },
{}
};
@ -1850,6 +2111,84 @@ static int parse_argv(int argc, char *argv[]) {
arg_no_pager = true;
break;
case ARG_SET_DNS: {
struct in_addr_data data, *n;
r = in_addr_from_string_auto(optarg, &data.family, &data.address);
if (r < 0)
return log_error_errno(r, "Failed to parse DNS server address: %s", optarg);
n = realloc(arg_set_dns, sizeof(struct in_addr_data) * (arg_n_set_dns + 1));
if (!n)
return log_oom();
arg_set_dns = n;
arg_set_dns[arg_n_set_dns++] = data;
arg_mode = MODE_SET_LINK;
break;
}
case ARG_SET_DOMAIN: {
const char *p;
p = optarg[0] == '~' ? optarg + 1 : optarg;
r = dns_name_is_valid(p);
if (r < 0)
return log_error_errno(r, "Failed to validate specified domain %s: %m", p);
if (r == 0)
return log_error_errno(r, "Domain not valid: %s", p);
r = strv_extend(&arg_set_domain, optarg);
if (r < 0)
return log_oom();
arg_mode = MODE_SET_LINK;
break;
}
case ARG_SET_LLMNR:
r = free_and_strdup(&arg_set_llmnr, optarg);
if (r < 0)
return log_oom();
arg_mode = MODE_SET_LINK;
break;
case ARG_SET_MDNS:
r = free_and_strdup(&arg_set_mdns, optarg);
if (r < 0)
return log_oom();
arg_mode = MODE_SET_LINK;
break;
case ARG_SET_DNSSEC:
r = free_and_strdup(&arg_set_dnssec, optarg);
if (r < 0)
return log_oom();
arg_mode = MODE_SET_LINK;
break;
case ARG_SET_NTA:
r = dns_name_is_valid(optarg);
if (r < 0)
return log_error_errno(r, "Failed to validate specified domain %s: %m", optarg);
if (r == 0)
return log_error_errno(r, "Domain not valid: %s", optarg);
r = strv_extend(&arg_set_nta, optarg);
if (r < 0)
return log_oom();
arg_mode = MODE_SET_LINK;
break;
case ARG_REVERT_LINK:
arg_mode = MODE_REVERT_LINK;
break;
case '?':
return -EINVAL;
@ -1873,6 +2212,19 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_class != 0 && arg_type == 0)
arg_type = DNS_TYPE_A;
if (IN_SET(arg_mode, MODE_SET_LINK, MODE_REVERT_LINK)) {
if (arg_ifindex <= 0) {
log_error("--set-dns=, --set-domain=, --set-llmnr=, --set-mdns=, --set-dnssec=, --set-nta= and --revert require --interface=.");
return -EINVAL;
}
if (arg_ifindex == LOOPBACK_IFINDEX) {
log_error("Interface can't be the loopback interface (lo). Sorry.");
return -EINVAL;
}
}
return 1 /* work to do */;
}
@ -2064,10 +2416,38 @@ int main(int argc, char **argv) {
r = status_all(bus);
break;
case MODE_SET_LINK:
if (argc > optind) {
log_error("Too many arguments.");
r = -EINVAL;
goto finish;
}
r = set_link(bus);
break;
case MODE_REVERT_LINK:
if (argc > optind) {
log_error("Too many arguments.");
r = -EINVAL;
goto finish;
}
r = revert_link(bus);
break;
}
finish:
pager_close();
free(arg_set_dns);
strv_free(arg_set_domain);
free(arg_set_llmnr);
free(arg_set_mdns);
free(arg_set_dnssec);
strv_free(arg_set_nta);
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}