diff --git a/man/rules/meson.build b/man/rules/meson.build index 196c6ef0d8..f5c21c2e44 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -614,7 +614,7 @@ manpages = [ 'ENABLE_RANDOMSEED'], ['systemd-rc-local-generator', '8', [], ''], ['systemd-remount-fs.service', '8', ['systemd-remount-fs'], ''], - ['systemd-resolve', '1', [], 'ENABLE_RESOLVE'], + ['systemd-resolve', '1', ['resolvconf'], 'ENABLE_RESOLVE'], ['systemd-resolved.service', '8', ['systemd-resolved'], 'ENABLE_RESOLVE'], ['systemd-rfkill.service', '8', diff --git a/man/systemd-resolve.xml b/man/systemd-resolve.xml index fd5e35954a..0c26102c05 100644 --- a/man/systemd-resolve.xml +++ b/man/systemd-resolve.xml @@ -47,7 +47,8 @@ systemd-resolve - Resolve domain names, IPV4 and IPv6 addresses, DNS resource records, and services + resolvconf + Resolve domain names, IPV4 and IPv6 addresses, DNS resource records, and services; introspect and reconfigure the DNS resolver @@ -134,6 +135,18 @@ --revert + + resolvconf + OPTIONS + -a INTERFACE < FILE + + + + resolvconf + OPTIONS + -d INTERFACE + + @@ -399,6 +412,82 @@ + + Compatibility with <citerefentry><refentrytitle>resolvconf</refentrytitle><manvolnum>8</manvolnum></citerefentry> + + systemd-resolve is a multi-call binary. When invoked as resolvconf + (generally achieved by means of a symbolic link of this name to the systemd-resolve binary) it + is run in a limited resolvconf8 + compatibility mode. It accepts mostly the same arguments and pushes all data into + systemd-resolved.service8, + similar to how and operate. Note that + systemd-resolved.service is the only supported backend, which is different from other + implementations of this command. Note that not all operations supported by other implementations are supported + natively. Specifically: + + + + + Registers per-interface DNS configuration data with + systemd-resolved. Expects a network interface name as only command line argument. Reads + resolv.conf5 compatible DNS + configuration data from its standard input. Relevant fields are nameserver and + domain/search. This command is mostly identical to invoking + systemd-resolve with a combination of and + . + + + + + Unregisters per-interface DNS configuration data with systemd-resolved. This + command is mostly identical to invoking systemd-resolve with + . + + + + + + When specified and will not complain about missing + network interfaces and will silently execute no operation in that case. + + + + + + This switch for "exclusive" operation is supported only partially. It is mapped to an + additional configured search domain of ~. — i.e. ensures that DNS traffic is preferably + routed to the DNS servers on this interface, unless there are other, more specific domains configured on other + interfaces. + + + + + + + These switches are not supported and are silently ignored. + + + + + + + + + + + + + + + + These switches are not supported and the command will fail if used. + + + + + See resolvconf8 for details on this command line options. + + Examples @@ -477,7 +566,8 @@ _443._tcp.fedoraproject.org IN TLSA 0 0 1 19400be5b7a31fb733917700789d2f0a2471c0 systemd1, systemd-resolved.service8, systemd.dnssd5, - systemd-networkd.service8 + systemd-networkd.service8, + resolvconf8 diff --git a/meson.build b/meson.build index 86d2025048..56e3d0086e 100644 --- a/meson.build +++ b/meson.build @@ -1632,6 +1632,10 @@ if conf.get('ENABLE_RESOLVE') == 1 install_rpath : rootlibexecdir, install : true) public_programs += [exe] + + meson.add_install_script(meson_make_symlink, + join_paths(bindir, 'systemd-resolve'), + join_paths(rootsbindir, 'resolvconf')) endif if conf.get('ENABLE_LOGIND') == 1 diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 16ba83ef88..122c735af3 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -80,7 +80,12 @@ systemd_resolved_sources = files(''' resolved-etc-hosts.c '''.split()) -systemd_resolve_sources = files('resolve-tool.c') +systemd_resolve_sources = files(''' + resolvconf-compat.c + resolvconf-compat.h + resolve-tool.c + resolve-tool.h +'''.split()) ############################################################ diff --git a/src/resolve/resolvconf-compat.c b/src/resolve/resolvconf-compat.c new file mode 100644 index 0000000000..7f48610701 --- /dev/null +++ b/src/resolve/resolvconf-compat.c @@ -0,0 +1,312 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include + +#include "alloc-util.h" +#include "def.h" +#include "dns-domain.h" +#include "extract-word.h" +#include "fileio.h" +#include "parse-util.h" +#include "resolvconf-compat.h" +#include "resolve-tool.h" +#include "resolved-def.h" +#include "string-util.h" +#include "strv.h" + +static void resolvconf_help(void) { + printf("%1$s -a INTERFACE < FILE\n" + "%1$s -d INTERFACE\n" + "\n" + "Register DNS server and domain configuration with systemd-resolved.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -a Register per-interface DNS server and domain data\n" + " -d Unregister per-interface DNS server and domain data\n" + " -f Ignore if specified interface does not exist\n" + " -x Send DNS traffic preferably over this interface\n" + "\n" + "This is a compatibility alias for the systemd-resolve(1) tool, providing native\n" + "command line compatibility with the resolvconf(8) tool of various Linux\n" + "distributions and BSD systems. Some options supported by other implementations\n" + "are not supported and are ignored: -m, -p. Various options supported by other\n" + "implementations are not supported and will cause the invocation to fail: -u,\n" + "-I, -i, -l, -R, -r, -v, -V, --enable-updates, --disable-updates,\n" + "--updates-are-enabled.\n" + , program_invocation_short_name); +} + +static int parse_nameserver(const char *string) { + int r; + + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + struct in_addr_data data, *n; + int ifindex = 0; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = in_addr_ifindex_from_string_auto(word, &data.family, &data.address, &ifindex); + if (r < 0) + return log_error_errno(r, "Failed to parse name server '%s': %m", word); + + if (ifindex > 0 && ifindex != arg_ifindex) { + log_error("Name server interface '%s' does not match selected interface: %m", word); + return -EINVAL; + } + + /* Some superficial filtering */ + if (in_addr_is_null(data.family, &data.address)) + continue; + if (data.family == AF_INET && data.address.in.s_addr == htobe32(INADDR_DNS_STUB)) /* resolved's own stub? */ + continue; + + n = reallocarray(arg_set_dns, arg_n_set_dns + 1, sizeof(struct in_addr_data)); + if (!n) + return log_oom(); + arg_set_dns = n; + + arg_set_dns[arg_n_set_dns++] = data; + } + + return 0; +} + +static int parse_search_domain(const char *string) { + int r; + + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); + if (r < 0) + return r; + if (r == 0) + break; + + r = dns_name_is_valid(word); + if (r < 0) + return log_error_errno(r, "Failed to validate specified domain '%s': %m", word); + if (r == 0) { + log_error("Domain not valid: %s", word); + return -EINVAL; + } + + if (strv_push(&arg_set_domain, word) < 0) + return log_oom(); + + word = NULL; + } + + return 0; +} + +int resolvconf_parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_ENABLE_UPDATES, + ARG_DISABLE_UPDATES, + ARG_UPDATES_ARE_ENABLED, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + + /* The following are specific to Debian's original resolvconf */ + { "enable-updates", no_argument, NULL, ARG_ENABLE_UPDATES }, + { "disable-updates", no_argument, NULL, ARG_DISABLE_UPDATES }, + { "updates-are-enabled", no_argument, NULL, ARG_UPDATES_ARE_ENABLED }, + {} + }; + + + enum { + TYPE_REGULAR, + TYPE_PRIVATE, /* -p: Not supported, treated identically to TYPE_REGULAR */ + TYPE_EXCLUSIVE, /* -x */ + } type = TYPE_REGULAR; + + const char *dot, *iface; + int c, r; + + assert(argc >= 0); + assert(argv); + + /* openresolv checks these environment variables */ + if (getenv("IF_EXCLUSIVE")) + type = TYPE_EXCLUSIVE; + if (getenv("IF_PRIVATE")) + type = TYPE_PRIVATE; /* not actually supported */ + + arg_mode = _MODE_INVALID; + + while ((c = getopt_long(argc, argv, "hadxpfm:uIi:l:Rr:vV", options, NULL)) >= 0) + switch(c) { + + case 'h': + resolvconf_help(); + return 0; /* done */; + + case ARG_VERSION: + return version(); + + /* -a and -d is what everybody can agree on */ + case 'a': + arg_mode = MODE_SET_LINK; + break; + + case 'd': + arg_mode = MODE_REVERT_LINK; + break; + + /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ + case 'x': + type = TYPE_EXCLUSIVE; + break; + + case 'p': + type = TYPE_PRIVATE; /* not actually supported */ + break; + + case 'f': + arg_ifindex_permissive = true; + break; + + /* The metrics stuff is an openresolv invention we ignore (and don't really need) */ + case 'm': + log_debug("Switch -%c ignored.", c); + break; + + /* Everybody else can agree on the existance of -u but we don't support it. */ + case 'u': + + /* The following options are openresolv inventions we don't support. */ + case 'I': + case 'i': + case 'l': + case 'R': + case 'r': + case 'v': + case 'V': + log_error("Switch -%c not supported.", c); + return -EINVAL; + + /* The Debian resolvconf commands we don't support. */ + case ARG_ENABLE_UPDATES: + log_error("Switch --enable-updates not supported."); + return -EINVAL; + case ARG_DISABLE_UPDATES: + log_error("Switch --disable-updates not supported."); + return -EINVAL; + case ARG_UPDATES_ARE_ENABLED: + log_error("Switch --updates-are-enabled not supported."); + return -EINVAL; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_mode == _MODE_INVALID) { + log_error("Expected either -a or -d on the command line."); + return -EINVAL; + } + + if (optind+1 != argc) { + log_error("Expected interface name as argument."); + return -EINVAL; + } + + dot = strchr(argv[optind], '.'); + if (dot) { + iface = strndupa(argv[optind], dot - argv[optind]); + log_debug("Ignoring protocol specifier '%s'.", dot + 1); + } else + iface = argv[optind]; + optind++; + + if (parse_ifindex(iface, &arg_ifindex) < 0) { + int ifi; + + ifi = if_nametoindex(iface); + if (ifi <= 0) { + if (errno == ENODEV && arg_ifindex_permissive) { + log_debug("Interface '%s' not found, but -f specified, ignoring.", iface); + return 0; /* done */ + } + + return log_error_errno(errno, "Unknown interface '%s': %m", iface); + } + + arg_ifindex = ifi; + } + + if (arg_mode == MODE_SET_LINK) { + unsigned n = 0; + + for (;;) { + _cleanup_free_ char *line = NULL; + const char *a, *l; + + r = read_line(stdin, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read from stdin: %m"); + if (r == 0) + break; + + n++; + + l = strstrip(line); + if (IN_SET(*l, '#', ';', 0)) + continue; + + a = first_word(l, "nameserver"); + if (a) { + (void) parse_nameserver(a); + continue; + } + + a = first_word(l, "domain"); + if (!a) + a = first_word(l, "search"); + if (a) { + (void) parse_search_domain(a); + continue; + } + + log_syntax(NULL, LOG_DEBUG, "stdin", n, 0, "Ignoring resolv.conf line: %s", l); + } + + if (type == TYPE_EXCLUSIVE) { + + /* If -x mode is selected, let's preferably route non-suffixed lookups to this interface. This + * somewhat matches the original -x behaviour */ + + r = strv_extend(&arg_set_domain, "~."); + if (r < 0) + return log_oom(); + + } else if (type == TYPE_PRIVATE) + log_debug("Private DNS server data not supported, ignoring."); + + if (arg_n_set_dns == 0) { + log_error("No DNS servers specified, refusing operation."); + return -EINVAL; + } + } + + return 1; /* work to do */ +} diff --git a/src/resolve/resolvconf-compat.h b/src/resolve/resolvconf-compat.h new file mode 100644 index 0000000000..507ac3d222 --- /dev/null +++ b/src/resolve/resolvconf-compat.h @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +int resolvconf_parse_argv(int argc, char *argv[]); diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c index fce86d1e74..04f883654a 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolve-tool.c @@ -36,18 +36,21 @@ #include "netlink-util.h" #include "pager.h" #include "parse-util.h" +#include "resolvconf-compat.h" +#include "resolve-tool.h" #include "resolved-def.h" #include "resolved-dns-packet.h" #include "strv.h" #include "terminal-util.h" static int arg_family = AF_UNSPEC; -static int arg_ifindex = 0; +int arg_ifindex = 0; static uint16_t arg_type = 0; static uint16_t arg_class = 0; static bool arg_legend = true; static uint64_t arg_flags = 0; static bool arg_no_pager = false; +bool arg_ifindex_permissive = false; /* If true, don't generate an error if the specified interface index doesn't exist */ typedef enum ServiceFamily { SERVICE_FAMILY_TCP, @@ -64,24 +67,11 @@ typedef enum RawType { } RawType; static RawType arg_raw = RAW_NONE; -static enum { - MODE_RESOLVE_HOST, - MODE_RESOLVE_RECORD, - MODE_RESOLVE_SERVICE, - MODE_RESOLVE_OPENPGP, - MODE_RESOLVE_TLSA, - 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; +ExecutionMode 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; +struct in_addr_data *arg_set_dns = NULL; +size_t arg_n_set_dns = 0; +char **arg_set_domain = NULL; static char *arg_set_llmnr = NULL; static char *arg_set_mdns = NULL; static char *arg_set_dnssec = NULL; @@ -1609,6 +1599,9 @@ static int set_link(sd_bus *bus) { if (q < 0) { if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) goto is_managed; + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; log_error_errno(q, "Failed to set DNS configuration: %s", bus_error_message(&error, q)); if (r == 0) @@ -1655,6 +1648,9 @@ static int set_link(sd_bus *bus) { if (q < 0) { if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) goto is_managed; + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; log_error_errno(q, "Failed to set domain configuration: %s", bus_error_message(&error, q)); if (r == 0) @@ -1777,8 +1773,13 @@ static int revert_link(sd_bus *bus) { &error, NULL, "i", arg_ifindex); - if (r < 0) + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + return log_error_errno(r, "Failed to revert interface configuration: %s", bus_error_message(&error, r)); + } return 0; } @@ -1815,7 +1816,7 @@ static void help_dns_classes(void) { } } -static void help(void) { +static void native_help(void) { printf("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" @@ -1858,7 +1859,7 @@ static void help(void) { , program_invocation_short_name); } -static int parse_argv(int argc, char *argv[]) { +static int native_parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_LEGEND, @@ -1926,7 +1927,7 @@ static int parse_argv(int argc, char *argv[]) { switch(c) { case 'h': - help(); + native_help(); return 0; /* done */; case ARG_VERSION: @@ -2234,7 +2235,10 @@ int main(int argc, char **argv) { log_parse_environment(); log_open(); - r = parse_argv(argc, argv); + if (streq(program_invocation_short_name, "resolvconf")) + r = resolvconf_parse_argv(argc, argv); + else + r = native_parse_argv(argc, argv); if (r <= 0) goto finish; @@ -2435,6 +2439,9 @@ int main(int argc, char **argv) { r = revert_link(bus); break; + + case _MODE_INVALID: + assert_not_reached("invalid mode"); } finish: diff --git a/src/resolve/resolve-tool.h b/src/resolve/resolve-tool.h new file mode 100644 index 0000000000..1189fa3ee0 --- /dev/null +++ b/src/resolve/resolve-tool.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include +#include + +extern int arg_ifindex; +extern bool arg_ifindex_permissive; + +typedef enum ExecutionMode { + MODE_RESOLVE_HOST, + MODE_RESOLVE_RECORD, + MODE_RESOLVE_SERVICE, + MODE_RESOLVE_OPENPGP, + MODE_RESOLVE_TLSA, + MODE_STATISTICS, + MODE_RESET_STATISTICS, + MODE_FLUSH_CACHES, + MODE_RESET_SERVER_FEATURES, + MODE_STATUS, + MODE_SET_LINK, + MODE_REVERT_LINK, + _MODE_INVALID = -1, +} ExecutionMode; + +extern ExecutionMode arg_mode; + +extern struct in_addr_data *arg_set_dns; +extern size_t arg_n_set_dns; +extern char **arg_set_domain; diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h index 96f93107ad..e1d72c19e2 100644 --- a/src/resolve/resolved-def.h +++ b/src/resolve/resolved-def.h @@ -41,3 +41,6 @@ #define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) #define SD_RESOLVED_QUERY_TIMEOUT_USEC (120 * USEC_PER_SEC) + +/* 127.0.0.53 in native endian */ +#define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U) diff --git a/src/resolve/resolved-dns-stub.h b/src/resolve/resolved-dns-stub.h index 197f2cc758..d47f1c12b2 100644 --- a/src/resolve/resolved-dns-stub.h +++ b/src/resolve/resolved-dns-stub.h @@ -22,8 +22,5 @@ #include "resolved-manager.h" -/* 127.0.0.53 in native endian */ -#define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U) - void manager_dns_stub_stop(Manager *m); int manager_dns_stub_start(Manager *m);