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 resolvconf8
+
+ 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);