summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/systemd-resolve.xml47
-rw-r--r--src/resolve/resolve-tool.c380
2 files changed, 425 insertions, 2 deletions
diff --git a/man/systemd-resolve.xml b/man/systemd-resolve.xml
index c4af2d8662..f924090553 100644
--- a/man/systemd-resolve.xml
+++ b/man/systemd-resolve.xml
@@ -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>
diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c
index f6d6a4bc1c..0252bdfcd7 100644
--- a/src/resolve/resolve-tool.c
+++ b/src/resolve/resolve-tool.c
@@ -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;
}