resolve: extend systemd-resolve so that it can push per-interface DNS configuration...
authorLennart Poettering <lennart@poettering.net>
Thu, 14 Dec 2017 19:13:14 +0000 (20:13 +0100)
committerGitHub <noreply@github.com>
Thu, 14 Dec 2017 19:13:14 +0000 (20:13 +0100)
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

man/systemd-resolve.xml
src/resolve/resolve-tool.c

index c4af2d8..f924090 100644 (file)
         <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>
index f6d6a4b..0252bdf 100644 (file)
 
 #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;
 }