]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolve-tool.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / resolve / resolve-tool.c
index a24bb546d4fef11ae7bf15a0eac45db02a10866a..a963454da0eb1ef5516b131ec88386ea0cde5c4d 100644 (file)
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <gcrypt.h>
 #include <getopt.h>
 #include <net/if.h>
 
 #include "sd-bus.h"
+#include "sd-netlink.h"
 
 #include "af-list.h"
 #include "alloc-util.h"
 #include "bus-error.h"
 #include "bus-util.h"
 #include "escape.h"
-#include "in-addr-util.h"
 #include "gcrypt-util.h"
+#include "in-addr-util.h"
+#include "netlink-util.h"
+#include "pager.h"
 #include "parse-util.h"
 #include "resolved-def.h"
 #include "resolved-dns-packet.h"
+#include "strv.h"
 #include "terminal-util.h"
 
-#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
+#define DNS_CALL_TIMEOUT_USEC (90*USEC_PER_SEC)
 
 static int arg_family = AF_UNSPEC;
 static int arg_ifindex = 0;
@@ -43,17 +47,59 @@ 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_raw = false;
+static bool arg_no_pager = false;
+
+typedef enum ServiceFamily {
+        SERVICE_FAMILY_TCP,
+        SERVICE_FAMILY_UDP,
+        SERVICE_FAMILY_SCTP,
+        _SERVICE_FAMILY_INVALID = -1,
+} ServiceFamily;
+static ServiceFamily arg_service_family = SERVICE_FAMILY_TCP;
+
+typedef enum RawType {
+        RAW_NONE,
+        RAW_PAYLOAD,
+        RAW_PACKET,
+} 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,
 } arg_mode = MODE_RESOLVE_HOST;
 
+static ServiceFamily service_family_from_string(const char *s) {
+        if (s == NULL || streq(s, "tcp"))
+                return SERVICE_FAMILY_TCP;
+        if (streq(s, "udp"))
+                return SERVICE_FAMILY_UDP;
+        if (streq(s, "sctp"))
+                return SERVICE_FAMILY_SCTP;
+        return _SERVICE_FAMILY_INVALID;
+}
+
+static const char* service_family_to_string(ServiceFamily service) {
+        switch(service) {
+        case SERVICE_FAMILY_TCP:
+                return "_tcp";
+        case SERVICE_FAMILY_UDP:
+                return "_udp";
+        case SERVICE_FAMILY_SCTP:
+                return "_sctp";
+        default:
+                assert_not_reached("invalid service");
+        }
+}
+
 static void print_source(uint64_t flags, usec_t rtt) {
         char rtt_str[FORMAT_TIMESTAMP_MAX];
 
@@ -70,8 +116,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 +207,7 @@ static int resolve_host(sd_bus *bus, const char *name) {
                 if (ifindex > 0 && !if_indextoname(ifindex, ifname))
                         log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex);
 
-                r = in_addr_to_string(family, a, &pretty);
+                r = in_addr_ifindex_to_string(family, a, ifindex, &pretty);
                 if (r < 0)
                         return log_error_errno(r, "Failed to print address for %s: %m", name);
 
@@ -215,7 +261,7 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a
         if (ifindex <= 0)
                 ifindex = arg_ifindex;
 
-        r = in_addr_to_string(family, address, &pretty);
+        r = in_addr_ifindex_to_string(family, address, ifindex, &pretty);
         if (r < 0)
                 return log_oom();
 
@@ -307,32 +353,51 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a
         return 0;
 }
 
-static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) {
-        const char *percent, *a;
-        int ifi = 0;
+static int output_rr_packet(const void *d, size_t l, int ifindex) {
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+        _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
         int r;
+        char ifname[IF_NAMESIZE] = "";
 
-        percent = strchr(s, '%');
-        if (percent) {
-                if (parse_ifindex(percent+1, &ifi) < 0) {
-                        ifi = if_nametoindex(percent+1);
-                        if (ifi <= 0)
-                                return -EINVAL;
-                }
+        r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX);
+        if (r < 0)
+                return log_oom();
 
-                a = strndupa(s, percent - s);
-        } else
-                a = s;
+        p->refuse_compression = true;
 
-        r = in_addr_from_string_auto(a, family, address);
+        r = dns_packet_append_blob(p, d, l, NULL);
         if (r < 0)
-                return r;
+                return log_oom();
+
+        r = dns_packet_read_rr(p, &rr, NULL, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse RR: %m");
+
+        if (arg_raw == RAW_PAYLOAD) {
+                void *data;
+                ssize_t k;
+
+                k = dns_resource_record_payload(rr, &data);
+                if (k < 0)
+                        return log_error_errno(k, "Cannot dump RR: %m");
+                fwrite(data, 1, k, stdout);
+        } else {
+                const char *s;
+
+                s = dns_resource_record_to_string(rr);
+                if (!s)
+                        return log_oom();
+
+                if (ifindex > 0 && !if_indextoname(ifindex, ifname))
+                        log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex);
+
+                printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname);
+        }
 
-        *ifindex = ifi;
         return 0;
 }
 
-static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) {
+static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type, bool warn_missing) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         char ifname[IF_NAMESIZE] = "";
@@ -367,7 +432,8 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_
 
         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
         if (r < 0) {
-                log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
+                if (warn_missing || r != -ENXIO)
+                        log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
                 return r;
         }
 
@@ -378,8 +444,6 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_
                 return bus_log_parse_error(r);
 
         while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) {
-                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
-                _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
                 uint16_t c, t;
                 int ifindex;
                 const void *d;
@@ -399,44 +463,17 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_
                 if (r < 0)
                         return bus_log_parse_error(r);
 
-                r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
-                if (r < 0)
-                        return log_oom();
-
-                p->refuse_compression = true;
+                if (arg_raw == RAW_PACKET) {
+                        uint64_t u64 = htole64(l);
 
-                r = dns_packet_append_blob(p, d, l, NULL);
-                if (r < 0)
-                        return log_oom();
-
-                r = dns_packet_read_rr(p, &rr, NULL, NULL);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to parse RR: %m");
-
-                if (arg_raw) {
-                        void *data;
-                        ssize_t k;
-
-                        k = dns_resource_record_payload(rr, &data);
-                        if (k < 0)
-                                return log_error_errno(k, "Cannot dump RR: %m");
-                        fwrite(data, 1, k, stdout);
+                        fwrite(&u64, sizeof(u64), 1, stdout);
+                        fwrite(d, 1, l, stdout);
                 } else {
-                        const char *s;
-
-                        s = dns_resource_record_to_string(rr);
-                        if (!s)
-                                return log_oom();
-
-                        ifname[0] = 0;
-                        if (ifindex > 0 && !if_indextoname(ifindex, ifname))
-                                log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex);
-
-                        printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname);
+                        r = output_rr_packet(d, l, ifindex);
+                        if (r < 0)
+                                return r;
                 }
 
-                printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname);
-
                 if (dns_type_needs_authentication(t))
                         needs_authentication = true;
 
@@ -454,7 +491,8 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_
                 return bus_log_parse_error(r);
 
         if (n == 0) {
-                log_error("%s: no records found", name);
+                if (warn_missing)
+                        log_error("%s: no records found", name);
                 return -ESRCH;
         }
 
@@ -584,7 +622,7 @@ static int resolve_rfc4501(sd_bus *bus, const char *name) {
         if (type == 0)
                 type = arg_type ?: DNS_TYPE_A;
 
-        return resolve_record(bus, n, class, type);
+        return resolve_record(bus, n, class, type, true);
 
 invalid:
         log_error("Invalid DNS URI: %s", name);
@@ -606,10 +644,8 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons
         assert(bus);
         assert(domain);
 
-        if (isempty(name))
-                name = NULL;
-        if (isempty(type))
-                type = NULL;
+        name = empty_to_null(name);
+        type = empty_to_null(type);
 
         if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname))
                 return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex);
@@ -746,7 +782,6 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        c = 0;
         while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) {
                 _cleanup_free_ char *escaped = NULL;
 
@@ -755,7 +790,6 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons
                         return log_oom();
 
                 printf("%*s%s\n", (int) indent, "", escaped);
-                c++;
         }
         if (r < 0)
                 return bus_log_parse_error(r);
@@ -768,10 +802,8 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        if (isempty(canonical_name))
-                canonical_name = NULL;
-        if (isempty(canonical_type))
-                canonical_type = NULL;
+        canonical_name = empty_to_null(canonical_name);
+        canonical_type = empty_to_null(canonical_type);
 
         if (!streq_ptr(name, canonical_name) ||
             !streq_ptr(type, canonical_type) ||
@@ -810,16 +842,66 @@ static int resolve_openpgp(sd_bus *bus, const char *address) {
         }
         domain++;
 
-        r = string_hashsum(address, domain - 1 - address, GCRY_MD_SHA224, &hashed);
+        r = string_hashsum_sha256(address, domain - 1 - address, &hashed);
         if (r < 0)
                 return log_error_errno(r, "Hashing failed: %m");
 
+        strshorten(hashed, 56);
+
         full = strjoina(hashed, "._openpgpkey.", domain);
         log_debug("Looking up \"%s\".", full);
 
+        r = resolve_record(bus, full,
+                           arg_class ?: DNS_CLASS_IN,
+                           arg_type ?: DNS_TYPE_OPENPGPKEY, false);
+
+        if (IN_SET(r, -ENXIO, -ESRCH)) { /* NXDOMAIN or NODATA? */
+              hashed = NULL;
+              r = string_hashsum_sha224(address, domain - 1 - address, &hashed);
+              if (r < 0)
+                    return log_error_errno(r, "Hashing failed: %m");
+
+              full = strjoina(hashed, "._openpgpkey.", domain);
+              log_debug("Looking up \"%s\".", full);
+
+              return resolve_record(bus, full,
+                                    arg_class ?: DNS_CLASS_IN,
+                                    arg_type ?: DNS_TYPE_OPENPGPKEY, true);
+        }
+
+        return r;
+}
+
+static int resolve_tlsa(sd_bus *bus, const char *address) {
+        const char *port;
+        uint16_t port_num = 443;
+        _cleanup_free_ char *full = NULL;
+        int r;
+
+        assert(bus);
+        assert(address);
+
+        port = strrchr(address, ':');
+        if (port) {
+                r = parse_ip_port(port + 1, &port_num);
+                if (r < 0)
+                        return log_error_errno(r, "Invalid port \"%s\".", port + 1);
+
+                address = strndupa(address, port - address);
+        }
+
+        r = asprintf(&full, "_%u.%s.%s",
+                     port_num,
+                     service_family_to_string(arg_service_family),
+                     address);
+        if (r < 0)
+                return log_oom();
+
+        log_debug("Looking up \"%s\".", full);
+
         return resolve_record(bus, full,
                               arg_class ?: DNS_CLASS_IN,
-                              arg_type ?: DNS_TYPE_OPENPGPKEY);
+                              arg_type ?: DNS_TYPE_TLSA, true);
 }
 
 static int show_statistics(sd_bus *bus) {
@@ -957,15 +1039,521 @@ static int reset_statistics(sd_bus *bus) {
         return 0;
 }
 
+static int flush_caches(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",
+                               "FlushCaches",
+                               &error,
+                               NULL,
+                               NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r));
+
+        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;
+
+        assert(bus);
+        assert(member);
+        assert(m);
+        assert(l);
+
+        r = sd_bus_message_enter_container(m, 'a', "(iay)");
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                const void *a;
+                char *pretty;
+                int family;
+                size_t sz;
+
+                r = sd_bus_message_enter_container(m, 'r', "iay");
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                r = sd_bus_message_read(m, "i", &family);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_read_array(m, 'y', &a, &sz);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_exit_container(m);
+                if (r < 0)
+                        return r;
+
+                if (!IN_SET(family, AF_INET, AF_INET6)) {
+                        log_debug("Unexpected family, ignoring.");
+                        continue;
+                }
+
+                if (sz != FAMILY_ADDRESS_SIZE(family)) {
+                        log_debug("Address size mismatch, ignoring.");
+                        continue;
+                }
+
+                r = in_addr_to_string(family, a, &pretty);
+                if (r < 0)
+                        return r;
+
+                r = strv_consume(l, pretty);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_exit_container(m);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int map_link_domains(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+        char ***l = userdata;
+        int r;
+
+        assert(bus);
+        assert(member);
+        assert(m);
+        assert(l);
+
+        r = sd_bus_message_enter_container(m, 'a', "(sb)");
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                const char *domain;
+                int route_only;
+                char *pretty;
+
+                r = sd_bus_message_read(m, "(sb)", &domain, &route_only);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (route_only)
+                        pretty = strappend("~", domain);
+                else
+                        pretty = strdup(domain);
+                if (!pretty)
+                        return -ENOMEM;
+
+                r = strv_consume(l, pretty);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_exit_container(m);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empty_line) {
+
+        struct link_info {
+                uint64_t scopes_mask;
+                char *llmnr;
+                char *mdns;
+                char *dnssec;
+                char **dns;
+                char **domains;
+                char **ntas;
+                int dnssec_supported;
+        } link_info = {};
+
+        static const struct bus_properties_map property_map[] = {
+                { "ScopesMask",                 "t",      NULL,                 offsetof(struct link_info, scopes_mask)      },
+                { "DNS",                        "a(iay)", map_link_dns_servers, offsetof(struct link_info, dns)              },
+                { "Domains",                    "a(sb)",  map_link_domains,     offsetof(struct link_info, domains)          },
+                { "LLMNR",                      "s",      NULL,                 offsetof(struct link_info, llmnr)            },
+                { "MulticastDNS",               "s",      NULL,                 offsetof(struct link_info, mdns)             },
+                { "DNSSEC",                     "s",      NULL,                 offsetof(struct link_info, dnssec)           },
+                { "DNSSECNegativeTrustAnchors", "as",     NULL,                 offsetof(struct link_info, ntas)             },
+                { "DNSSECSupported",            "b",      NULL,                 offsetof(struct link_info, dnssec_supported) },
+                {}
+        };
+
+        _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;
+        int r;
+
+        assert(bus);
+        assert(ifindex > 0);
+        assert(empty_line);
+
+        if (!name) {
+                if (!if_indextoname(ifindex, ifname))
+                        return log_error_errno(errno, "Failed to resolve interface name for %i: %m", ifindex);
+
+                name = ifname;
+        }
+
+        if (asprintf(&ifi, "%i", ifindex) < 0)
+                return log_oom();
+
+        r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifi, &p);
+        if (r < 0)
+                return log_oom();
+
+        r = bus_map_all_properties(bus,
+                                   "org.freedesktop.resolve1",
+                                   p,
+                                   property_map,
+                                   &error,
+                                   &link_info);
+        if (r < 0) {
+                log_error_errno(r, "Failed to get link data for %i: %s", ifindex, bus_error_message(&error, r));
+                goto finish;
+        }
+
+        pager_open(arg_no_pager, false);
+
+        if (*empty_line)
+                fputc('\n', stdout);
+
+        printf("%sLink %i (%s)%s\n",
+               ansi_highlight(), ifindex, name, ansi_normal());
+
+        if (link_info.scopes_mask == 0)
+                printf("      Current Scopes: none\n");
+        else
+                printf("      Current Scopes:%s%s%s%s%s\n",
+                       link_info.scopes_mask & SD_RESOLVED_DNS ? " DNS" : "",
+                       link_info.scopes_mask & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "",
+                       link_info.scopes_mask & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "",
+                       link_info.scopes_mask & SD_RESOLVED_MDNS_IPV4 ? " mDNS/IPv4" : "",
+                       link_info.scopes_mask & SD_RESOLVED_MDNS_IPV6 ? " mDNS/IPv6" : "");
+
+        printf("       LLMNR setting: %s\n"
+               "MulticastDNS setting: %s\n"
+               "      DNSSEC setting: %s\n"
+               "    DNSSEC supported: %s\n",
+               strna(link_info.llmnr),
+               strna(link_info.mdns),
+               strna(link_info.dnssec),
+               yes_no(link_info.dnssec_supported));
+
+        STRV_FOREACH(i, link_info.dns) {
+                printf("         %s %s\n",
+                       i == link_info.dns ? "DNS Servers:" : "            ",
+                       *i);
+        }
+
+        STRV_FOREACH(i, link_info.domains) {
+                printf("          %s %s\n",
+                       i == link_info.domains ? "DNS Domain:" : "           ",
+                       *i);
+        }
+
+        STRV_FOREACH(i, link_info.ntas) {
+                printf("          %s %s\n",
+                       i == link_info.ntas ? "DNSSEC NTA:" : "           ",
+                       *i);
+        }
+
+        *empty_line = true;
+
+        r = 0;
+
+finish:
+        strv_free(link_info.dns);
+        strv_free(link_info.domains);
+        free(link_info.llmnr);
+        free(link_info.mdns);
+        free(link_info.dnssec);
+        strv_free(link_info.ntas);
+        return r;
+}
+
+static int map_global_dns_servers(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+        char ***l = userdata;
+        int r;
+
+        assert(bus);
+        assert(member);
+        assert(m);
+        assert(l);
+
+        r = sd_bus_message_enter_container(m, 'a', "(iiay)");
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                const void *a;
+                char *pretty;
+                int family, ifindex;
+                size_t sz;
+
+                r = sd_bus_message_enter_container(m, 'r', "iiay");
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                r = sd_bus_message_read(m, "ii", &ifindex, &family);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_read_array(m, 'y', &a, &sz);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_exit_container(m);
+                if (r < 0)
+                        return r;
+
+                if (ifindex != 0) /* only show the global ones here */
+                        continue;
+
+                if (!IN_SET(family, AF_INET, AF_INET6)) {
+                        log_debug("Unexpected family, ignoring.");
+                        continue;
+                }
+
+                if (sz != FAMILY_ADDRESS_SIZE(family)) {
+                        log_debug("Address size mismatch, ignoring.");
+                        continue;
+                }
+
+                r = in_addr_to_string(family, a, &pretty);
+                if (r < 0)
+                        return r;
+
+                r = strv_consume(l, pretty);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_exit_container(m);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int map_global_domains(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+        char ***l = userdata;
+        int r;
+
+        assert(bus);
+        assert(member);
+        assert(m);
+        assert(l);
+
+        r = sd_bus_message_enter_container(m, 'a', "(isb)");
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                const char *domain;
+                int route_only, ifindex;
+                char *pretty;
+
+                r = sd_bus_message_read(m, "(isb)", &ifindex, &domain, &route_only);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (ifindex != 0) /* only show the global ones here */
+                        continue;
+
+                if (route_only)
+                        pretty = strappend("~", domain);
+                else
+                        pretty = strdup(domain);
+                if (!pretty)
+                        return -ENOMEM;
+
+                r = strv_consume(l, pretty);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_exit_container(m);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int status_global(sd_bus *bus, bool *empty_line) {
+
+        struct global_info {
+                char **dns;
+                char **domains;
+                char **ntas;
+        } global_info = {};
+
+        static const struct bus_properties_map property_map[] = {
+                { "DNS",                        "a(iiay)", map_global_dns_servers, offsetof(struct global_info, dns)     },
+                { "Domains",                    "a(isb)",  map_global_domains,     offsetof(struct global_info, domains) },
+                { "DNSSECNegativeTrustAnchors", "as",      NULL,                   offsetof(struct global_info, ntas)    },
+                {}
+        };
+
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        char **i;
+        int r;
+
+        assert(bus);
+        assert(empty_line);
+
+        r = bus_map_all_properties(bus,
+                                   "org.freedesktop.resolve1",
+                                   "/org/freedesktop/resolve1",
+                                   property_map,
+                                   &error,
+                                   &global_info);
+        if (r < 0) {
+                log_error_errno(r, "Failed to get global data: %s", bus_error_message(&error, r));
+                goto finish;
+        }
+
+        if (strv_isempty(global_info.dns) && strv_isempty(global_info.domains) && strv_isempty(global_info.ntas)) {
+                r = 0;
+                goto finish;
+        }
+
+        pager_open(arg_no_pager, false);
+
+        printf("%sGlobal%s\n", ansi_highlight(), ansi_normal());
+        STRV_FOREACH(i, global_info.dns) {
+                printf("         %s %s\n",
+                       i == global_info.dns ? "DNS Servers:" : "            ",
+                       *i);
+        }
+
+        STRV_FOREACH(i, global_info.domains) {
+                printf("          %s %s\n",
+                       i == global_info.domains ? "DNS Domain:" : "           ",
+                       *i);
+        }
+
+        strv_sort(global_info.ntas);
+        STRV_FOREACH(i, global_info.ntas) {
+                printf("          %s %s\n",
+                       i == global_info.ntas ? "DNSSEC NTA:" : "           ",
+                       *i);
+        }
+
+        *empty_line = true;
+
+        r = 0;
+
+finish:
+        strv_free(global_info.dns);
+        strv_free(global_info.domains);
+        strv_free(global_info.ntas);
+
+        return r;
+}
+
+static int status_all(sd_bus *bus) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        sd_netlink_message *i;
+        bool empty_line = false;
+        int r;
+
+        assert(bus);
+
+        r = status_global(bus, &empty_line);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_open(&rtnl);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to netlink: %m");
+
+        r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
+        if (r < 0)
+                return rtnl_log_create_error(r);
+
+        r = sd_netlink_message_request_dump(req, true);
+        if (r < 0)
+                return rtnl_log_create_error(r);
+
+        r = sd_netlink_call(rtnl, req, 0, &reply);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enumerate links: %m");
+
+        r = 0;
+        for (i = reply; i; i = sd_netlink_message_next(i)) {
+                const char *name;
+                int ifindex, q;
+                uint16_t type;
+
+                q = sd_netlink_message_get_type(i, &type);
+                if (q < 0)
+                        return rtnl_log_parse_error(q);
+
+                if (type != RTM_NEWLINK)
+                        continue;
+
+                q = sd_rtnl_message_link_get_ifindex(i, &ifindex);
+                if (q < 0)
+                        return rtnl_log_parse_error(q);
+
+                if (ifindex == LOOPBACK_IFINDEX)
+                        continue;
+
+                q = sd_netlink_message_read_string(i, IFLA_IFNAME, &name);
+                if (q < 0)
+                        return rtnl_log_parse_error(q);
+
+                q = status_ifindex(bus, ifindex, name, &empty_line);
+                if (q < 0 && r >= 0)
+                        r = q;
+        }
+
+        return r;
+}
+
 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\nmnds-ipv4\nmdns-ipv6");
 }
 
 static void help_dns_types(void) {
-        int i;
         const char *t;
+        int i;
 
         if (arg_legend)
                 puts("Known DNS RR types:");
@@ -977,8 +1565,8 @@ static void help_dns_types(void) {
 }
 
 static void help_dns_classes(void) {
-        int i;
         const char *t;
+        int i;
 
         if (arg_legend)
                 puts("Known DNS RR classes:");
@@ -996,9 +1584,10 @@ static void help(void) {
                "%1$s [OPTIONS...] --statistics\n"
                "%1$s [OPTIONS...] --reset-statistics\n"
                "\n"
-               "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n"
+               "Resolve domain names, IPv4 and IPv6 addresses, DNS records, and services.\n\n"
                "  -h --help                 Show this help\n"
                "     --version              Show package version\n"
+               "     --no-pager             Do not pipe output into a pager\n"
                "  -4                        Resolve IPv4 addresses\n"
                "  -6                        Resolve IPv6 addresses\n"
                "  -i --interface=INTERFACE  Look on interface\n"
@@ -1009,12 +1598,18 @@ static void help(void) {
                "     --service-address=BOOL Resolve address for services (default: yes)\n"
                "     --service-txt=BOOL     Resolve TXT records for services (default: yes)\n"
                "     --openpgp              Query OpenPGP public key\n"
+               "     --tlsa                 Query TLS public key\n"
                "     --cname=BOOL           Follow CNAME redirects (default: yes)\n"
                "     --search=BOOL          Use search domains for single-label names\n"
                "                                                              (default: yes)\n"
+               "     --raw[=payload|packet] Dump the answer as binary data\n"
                "     --legend=BOOL          Print headers and additional info (default: yes)\n"
                "     --statistics           Show resolver statistics\n"
                "     --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"
                , program_invocation_short_name);
 }
 
@@ -1027,29 +1622,39 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_SERVICE_ADDRESS,
                 ARG_SERVICE_TXT,
                 ARG_OPENPGP,
+                ARG_TLSA,
                 ARG_RAW,
                 ARG_SEARCH,
                 ARG_STATISTICS,
                 ARG_RESET_STATISTICS,
+                ARG_STATUS,
+                ARG_FLUSH_CACHES,
+                ARG_RESET_SERVER_FEATURES,
+                ARG_NO_PAGER,
         };
 
         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          },
-                { "raw",              no_argument,       NULL, ARG_RAW              },
-                { "search",           required_argument, NULL, ARG_SEARCH           },
-                { "statistics",       no_argument,       NULL, ARG_STATISTICS,      },
-                { "reset-statistics", no_argument,       NULL, ARG_RESET_STATISTICS },
+                { "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              },
                 {}
         };
 
@@ -1145,6 +1750,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;
@@ -1160,13 +1771,30 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_mode = MODE_RESOLVE_OPENPGP;
                         break;
 
+                case ARG_TLSA:
+                        arg_mode = MODE_RESOLVE_TLSA;
+                        arg_service_family = service_family_from_string(optarg);
+                        if (arg_service_family < 0) {
+                                log_error("Unknown service family \"%s\".", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
                 case ARG_RAW:
                         if (on_tty()) {
                                 log_error("Refusing to write binary data to tty.");
                                 return -ENOTTY;
                         }
 
-                        arg_raw = true;
+                        if (optarg == NULL || streq(optarg, "payload"))
+                                arg_raw = RAW_PAYLOAD;
+                        else if (streq(optarg, "packet"))
+                                arg_raw = RAW_PACKET;
+                        else {
+                                log_error("Unknown --raw specifier \"%s\".", optarg);
+                                return -EINVAL;
+                        }
+
                         arg_legend = false;
                         break;
 
@@ -1174,40 +1802,28 @@ static int parse_argv(int argc, char *argv[]) {
                         r = parse_boolean(optarg);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to parse --cname= argument.");
-                        if (r == 0)
-                                arg_flags |= SD_RESOLVED_NO_CNAME;
-                        else
-                                arg_flags &= ~SD_RESOLVED_NO_CNAME;
+                        SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0);
                         break;
 
                 case ARG_SERVICE_ADDRESS:
                         r = parse_boolean(optarg);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to parse --service-address= argument.");
-                        if (r == 0)
-                                arg_flags |= SD_RESOLVED_NO_ADDRESS;
-                        else
-                                arg_flags &= ~SD_RESOLVED_NO_ADDRESS;
+                        SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0);
                         break;
 
                 case ARG_SERVICE_TXT:
                         r = parse_boolean(optarg);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to parse --service-txt= argument.");
-                        if (r == 0)
-                                arg_flags |= SD_RESOLVED_NO_TXT;
-                        else
-                                arg_flags &= ~SD_RESOLVED_NO_TXT;
+                        SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0);
                         break;
 
                 case ARG_SEARCH:
                         r = parse_boolean(optarg);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to parse --search argument.");
-                        if (r == 0)
-                                arg_flags |= SD_RESOLVED_NO_SEARCH;
-                        else
-                                arg_flags &= ~SD_RESOLVED_NO_SEARCH;
+                        SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0);
                         break;
 
                 case ARG_STATISTICS:
@@ -1218,6 +1834,22 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_mode = MODE_RESET_STATISTICS;
                         break;
 
+                case ARG_FLUSH_CACHES:
+                        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;
+
+                case ARG_NO_PAGER:
+                        arg_no_pager = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1230,7 +1862,7 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
-        if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) {
+        if (arg_type != 0 && arg_mode == MODE_RESOLVE_SERVICE) {
                 log_error("--service and --type= may not be combined.");
                 return -EINVAL;
         }
@@ -1277,7 +1909,7 @@ int main(int argc, char **argv) {
                         if (startswith(argv[optind], "dns:"))
                                 k = resolve_rfc4501(bus, argv[optind]);
                         else {
-                                k = parse_address(argv[optind], &family, &a, &ifindex);
+                                k = in_addr_ifindex_from_string_auto(argv[optind], &family, &a, &ifindex);
                                 if (k >= 0)
                                         k = resolve_address(bus, family, &a, ifindex);
                                 else
@@ -1301,7 +1933,7 @@ int main(int argc, char **argv) {
                 while (argv[optind]) {
                         int k;
 
-                        k = resolve_record(bus, argv[optind], arg_class, arg_type);
+                        k = resolve_record(bus, argv[optind], arg_class, arg_type, true);
                         if (r == 0)
                                 r = k;
 
@@ -1347,6 +1979,24 @@ int main(int argc, char **argv) {
                 }
                 break;
 
+        case MODE_RESOLVE_TLSA:
+                if (argc < optind + 1) {
+                        log_error("Domain name required.");
+                        r = -EINVAL;
+                        goto finish;
+
+                }
+
+                r = 0;
+                while (optind < argc) {
+                        int k;
+
+                        k = resolve_tlsa(bus, argv[optind++]);
+                        if (k < 0)
+                                r = k;
+                }
+                break;
+
         case MODE_STATISTICS:
                 if (argc > optind) {
                         log_error("Too many arguments.");
@@ -1366,8 +2016,58 @@ int main(int argc, char **argv) {
 
                 r = reset_statistics(bus);
                 break;
+
+        case MODE_FLUSH_CACHES:
+                if (argc > optind) {
+                        log_error("Too many arguments.");
+                        r = -EINVAL;
+                        goto finish;
+                }
+
+                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) {
+                        char **ifname;
+                        bool empty_line = false;
+
+                        r = 0;
+                        STRV_FOREACH(ifname, argv + optind) {
+                                int ifindex, q;
+
+                                q = parse_ifindex(argv[optind], &ifindex);
+                                if (q < 0) {
+                                        ifindex = if_nametoindex(argv[optind]);
+                                        if (ifindex <= 0) {
+                                                log_error_errno(errno, "Failed to resolve interface name: %s", argv[optind]);
+                                                continue;
+                                        }
+                                }
+
+                                q = status_ifindex(bus, ifindex, NULL, &empty_line);
+                                if (q < 0 && r >= 0)
+                                        r = q;
+                        }
+                } else
+                        r = status_all(bus);
+
+                break;
         }
 
 finish:
+        pager_close();
+
         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 }