]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolve-tool.c
resolve-tool: propagate sensible errors from due to dns_name_is_valid() check failures
[thirdparty/systemd.git] / src / resolve / resolve-tool.c
index 07d9582ccb243c5742835e47aff3c420d4719fc6..d4aac244be92d14a35b2a0dbeb1e8a2a5ac0d720 100644 (file)
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
 
 #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"
@@ -38,8 +41,6 @@
 #include "strv.h"
 #include "terminal-util.h"
 
-#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
-
 static int arg_family = AF_UNSPEC;
 static int arg_ifindex = 0;
 static uint16_t arg_type = 0;
@@ -72,11 +73,22 @@ static enum {
         MODE_STATISTICS,
         MODE_RESET_STATISTICS,
         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"))
+        if (!s || streq(s, "tcp"))
                 return SERVICE_FAMILY_TCP;
         if (streq(s, "udp"))
                 return SERVICE_FAMILY_UDP;
@@ -114,8 +126,8 @@ static void print_source(uint64_t flags, usec_t rtt) {
                        flags & SD_RESOLVED_DNS ? " DNS" :"",
                        flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "",
                        flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "",
-                       flags & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4" : "",
-                       flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : "");
+                       flags & SD_RESOLVED_MDNS_IPV4 ? " mDNS/IPv4" : "",
+                       flags & SD_RESOLVED_MDNS_IPV6 ? " mDNS/IPv6" : "");
 
         assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100));
 
@@ -161,7 +173,7 @@ static int resolve_host(sd_bus *bus, const char *name) {
 
         ts = now(CLOCK_MONOTONIC);
 
-        r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
+        r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply);
         if (r < 0)
                 return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r));
 
@@ -292,7 +304,7 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a
 
         ts = now(CLOCK_MONOTONIC);
 
-        r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
+        r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply);
         if (r < 0) {
                 log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r));
                 return r;
@@ -357,7 +369,7 @@ static int output_rr_packet(const void *d, size_t l, int ifindex) {
         int r;
         char ifname[IF_NAMESIZE] = "";
 
-        r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
+        r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX);
         if (r < 0)
                 return log_oom();
 
@@ -428,7 +440,7 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_
 
         ts = now(CLOCK_MONOTONIC);
 
-        r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
+        r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply);
         if (r < 0) {
                 if (warn_missing || r != -ENXIO)
                         log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
@@ -671,7 +683,7 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons
 
         ts = now(CLOCK_MONOTONIC);
 
-        r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
+        r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply);
         if (r < 0)
                 return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r));
 
@@ -1055,6 +1067,24 @@ static int flush_caches(sd_bus *bus) {
         return 0;
 }
 
+static int reset_server_features(sd_bus *bus) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        r = sd_bus_call_method(bus,
+                               "org.freedesktop.resolve1",
+                               "/org/freedesktop/resolve1",
+                               "org.freedesktop.resolve1.Manager",
+                               "ResetServerFeatures",
+                               &error,
+                               NULL,
+                               NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r));
+
+        return 0;
+}
+
 static int map_link_dns_servers(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
         char ***l = userdata;
         int r;
@@ -1186,6 +1216,7 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empt
                 {}
         };
 
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_free_ char *ifi = NULL, *p = NULL;
         char ifname[IF_NAMESIZE] = "";
         char **i;
@@ -1213,9 +1244,10 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empt
                                    "org.freedesktop.resolve1",
                                    p,
                                    property_map,
+                                   &error,
                                    &link_info);
         if (r < 0) {
-                log_error_errno(r, "Failed to get link data for %i: %m", ifindex);
+                log_error_errno(r, "Failed to get link data for %i: %s", ifindex, bus_error_message(&error, r));
                 goto finish;
         }
 
@@ -1405,6 +1437,7 @@ static int status_global(sd_bus *bus, bool *empty_line) {
                 {}
         };
 
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         char **i;
         int r;
 
@@ -1415,9 +1448,10 @@ static int status_global(sd_bus *bus, bool *empty_line) {
                                    "org.freedesktop.resolve1",
                                    "/org/freedesktop/resolve1",
                                    property_map,
+                                   &error,
                                    &global_info);
         if (r < 0) {
-                log_error_errno(r, "Failed to get global data: %m");
+                log_error_errno(r, "Failed to get global data: %s", bus_error_message(&error, r));
                 goto finish;
         }
 
@@ -1521,10 +1555,238 @@ 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:");
-        puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6");
+        puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6\nmdns\nmdns-ipv4\nmdns-ipv6");
 }
 
 static void help_dns_types(void) {
@@ -1584,6 +1846,15 @@ static void help(void) {
                "     --reset-statistics     Reset resolver statistics\n"
                "     --status               Show link and server status\n"
                "     --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);
 }
 
@@ -1603,30 +1874,46 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_RESET_STATISTICS,
                 ARG_STATUS,
                 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[] = {
-                { "help",             no_argument,       NULL, 'h'                  },
-                { "version",          no_argument,       NULL, ARG_VERSION          },
-                { "type",             required_argument, NULL, 't'                  },
-                { "class",            required_argument, NULL, 'c'                  },
-                { "legend",           required_argument, NULL, ARG_LEGEND           },
-                { "interface",        required_argument, NULL, 'i'                  },
-                { "protocol",         required_argument, NULL, 'p'                  },
-                { "cname",            required_argument, NULL, ARG_CNAME            },
-                { "service",          no_argument,       NULL, ARG_SERVICE          },
-                { "service-address",  required_argument, NULL, ARG_SERVICE_ADDRESS  },
-                { "service-txt",      required_argument, NULL, ARG_SERVICE_TXT      },
-                { "openpgp",          no_argument,       NULL, ARG_OPENPGP          },
-                { "tlsa",             optional_argument, NULL, ARG_TLSA             },
-                { "raw",              optional_argument, NULL, ARG_RAW              },
-                { "search",           required_argument, NULL, ARG_SEARCH           },
-                { "statistics",       no_argument,       NULL, ARG_STATISTICS,      },
-                { "reset-statistics", no_argument,       NULL, ARG_RESET_STATISTICS },
-                { "status",           no_argument,       NULL, ARG_STATUS           },
-                { "flush-caches",     no_argument,       NULL, ARG_FLUSH_CACHES     },
-                { "no-pager",         no_argument,       NULL, ARG_NO_PAGER         },
+                { "help",                  no_argument,       NULL, 'h'                       },
+                { "version",               no_argument,       NULL, ARG_VERSION               },
+                { "type",                  required_argument, NULL, 't'                       },
+                { "class",                 required_argument, NULL, 'c'                       },
+                { "legend",                required_argument, NULL, ARG_LEGEND                },
+                { "interface",             required_argument, NULL, 'i'                       },
+                { "protocol",              required_argument, NULL, 'p'                       },
+                { "cname",                 required_argument, NULL, ARG_CNAME                 },
+                { "service",               no_argument,       NULL, ARG_SERVICE               },
+                { "service-address",       required_argument, NULL, ARG_SERVICE_ADDRESS       },
+                { "service-txt",           required_argument, NULL, ARG_SERVICE_TXT           },
+                { "openpgp",               no_argument,       NULL, ARG_OPENPGP               },
+                { "tlsa",                  optional_argument, NULL, ARG_TLSA                  },
+                { "raw",                   optional_argument, NULL, ARG_RAW                   },
+                { "search",                required_argument, NULL, ARG_SEARCH                },
+                { "statistics",            no_argument,       NULL, ARG_STATISTICS,           },
+                { "reset-statistics",      no_argument,       NULL, ARG_RESET_STATISTICS      },
+                { "status",                no_argument,       NULL, ARG_STATUS                },
+                { "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           },
                 {}
         };
 
@@ -1653,12 +1940,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_family = AF_INET6;
                         break;
 
-                case 'i': {
-                        int ifi;
+                case 'i':
+                        if (parse_ifindex(optarg, &arg_ifindex) < 0) {
+                                int ifi;
 
-                        if (parse_ifindex(optarg, &ifi) >= 0)
-                                arg_ifindex = ifi;
-                        else {
                                 ifi = if_nametoindex(optarg);
                                 if (ifi <= 0)
                                         return log_error_errno(errno, "Unknown interface %s: %m", optarg);
@@ -1667,7 +1952,6 @@ static int parse_argv(int argc, char *argv[]) {
                         }
 
                         break;
-                }
 
                 case 't':
                         if (streq(optarg, "help")) {
@@ -1722,6 +2006,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 arg_flags |= SD_RESOLVED_LLMNR_IPV4;
                         else if (streq(optarg, "llmnr-ipv6"))
                                 arg_flags |= SD_RESOLVED_LLMNR_IPV6;
+                        else if (streq(optarg, "mdns"))
+                                arg_flags |= SD_RESOLVED_MDNS;
+                        else if (streq(optarg, "mdns-ipv4"))
+                                arg_flags |= SD_RESOLVED_MDNS_IPV4;
+                        else if (streq(optarg, "mdns-ipv6"))
+                                arg_flags |= SD_RESOLVED_MDNS_IPV6;
                         else {
                                 log_error("Unknown protocol specifier: %s", optarg);
                                 return -EINVAL;
@@ -1804,6 +2094,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_mode = MODE_FLUSH_CACHES;
                         break;
 
+                case ARG_RESET_SERVER_FEATURES:
+                        arg_mode = MODE_RESET_SERVER_FEATURES;
+                        break;
+
                 case ARG_STATUS:
                         arg_mode = MODE_STATUS;
                         break;
@@ -1812,6 +2106,88 @@ 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) {
+                                log_error("Domain not valid: %s", p);
+                                return -EINVAL;
+                        }
+
+                        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) {
+                                log_error("Domain not valid: %s", optarg);
+                                return -EINVAL;
+                        }
+
+                        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;
 
@@ -1835,6 +2211,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 */;
 }
 
@@ -1989,6 +2378,16 @@ int main(int argc, char **argv) {
                 r = flush_caches(bus);
                 break;
 
+        case MODE_RESET_SERVER_FEATURES:
+                if (argc > optind) {
+                        log_error("Too many arguments.");
+                        r = -EINVAL;
+                        goto finish;
+                }
+
+                r = reset_server_features(bus);
+                break;
+
         case MODE_STATUS:
 
                 if (argc > optind) {
@@ -2016,10 +2415,37 @@ 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;
 }