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 @@ Shows the global and per-link DNS settings in currently in effect. + + + + + + + + + Set per-interface DNS configuration. These switches may be used to configure various DNS + settings for network interfaces that aren't managed by + systemd-networkd.service8. (These + commands will fail when used on interfaces that are managed by systemd-networkd, please + configure their DNS settings directly inside the .network files instead.) These switches + may be used to inform systemd-resolved about per-interface DNS configuration determined + through external means. Multiple of these switches may be passed on a single invocation of + systemd-resolve in order to set multiple configuration options at once. If any of these + switches is used, it must be combined with to indicate the network interface the + new DNS configuration belongs to. The 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 expects a valid DNS domain, possibly prefixed with + ~, and configures a per-interface search or route-only domain. It may be used multiple times + to configure multiple such domains. The , and + options may be used to configure the per-interface LLMNR, MulticastDNS and + DNSSEC settings. Finally, 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 + systemd.network5. + + + + + + + Revert the per-interface DNS configuration. This option must be combined with + 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 , , , + , , . Note that when a + network interface disappears all configuration is lost automatically, an explicit reverting is not necessary in + that case. + + @@ -403,8 +445,9 @@ _443._tcp.fedoraproject.org IN TLSA 0 0 1 19400be5b7a31fb733917700789d2f0a2471c0 See Also systemd1, - systemd-resolved.service8 - systemd.dnssd5 + systemd-resolved.service8, + systemd.dnssd5, + systemd-networkd.service8 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; }