]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolvectl: implement --json flag for resolvectl status
authorNick Rosbrook <enr0n@ubuntu.com>
Fri, 10 Oct 2025 19:56:36 +0000 (15:56 -0400)
committerNick Rosbrook <enr0n@ubuntu.com>
Thu, 6 Nov 2025 10:17:59 +0000 (05:17 -0500)
Add --json support for all status commands in resolvectl by making use
of the new DumpDNSConfiguration varlink method. E.g,

$ resolvectl --json=pretty status eth0
[
{
"ifname" : "eth0",
"ifindex" : 9,
"defaultRoute" : true,
"currentServer" : {
                        "addressString" : "10.148.181.1",
"address" : [
10,
148,
181,
1
],
"family" : 2,
"port" : 53,
"ifindex" : 9,
"accessible" : true
},
"servers" : [
{
                                "addressString" : "10.148.181.1",
"address" : [
10,
148,
181,
1
],
"family" : 2,
"port" : 53,
"ifindex" : 9,
"accessible" : true
}
],
"searchDomains" : [
{
"name" : "local",
"routeOnly" : false,
"ifindex" : 9
}
],
"dnssec" : "allow-downgrade",
"dnsOverTLS" : "no",
"llmnr" : "no",
"mDNS" : "no",
"scopes" : [
{
"protocol" : "dns",
"ifindex" : 9,
"ifname" : "eth0",
"dnssec" : "allow-downgrade",
"dnsOverTLS" : "no"
}
]
}
]

Like the regular status output, fields are omitted all together when
empty, unless explicitly requested via one of the sub-commands dns,
domain, nta, etc.

src/resolve/resolvectl.c

index 13f68f87b9ec230daf81a75633f47616f4ad5abd..e4125766541b90e719c4780559f4cb6dac406c5a 100644 (file)
@@ -45,6 +45,7 @@
 #include "resolvectl.h"
 #include "resolved-def.h"
 #include "resolved-util.h"
+#include "set.h"
 #include "socket-netlink.h"
 #include "sort-util.h"
 #include "stdio-util.h"
@@ -1792,6 +1793,161 @@ static char** global_protocol_status(const GlobalInfo *info) {
         return TAKE_PTR(s);
 }
 
+static const char* status_mode_to_json_field(StatusMode mode) {
+        switch (mode) {
+
+        case STATUS_ALL:
+                return NULL;
+
+        case STATUS_DNS:
+                return "servers";
+
+        case STATUS_DOMAIN:
+                return "searchDomains";
+
+        case STATUS_DEFAULT_ROUTE:
+                return "defaultRoute";
+
+        case STATUS_LLMNR:
+                return "llmnr";
+
+        case STATUS_MDNS:
+                return "mDNS";
+
+        case STATUS_PRIVATE:
+                return "dnsOverTLS";
+
+        case STATUS_DNSSEC:
+                return "dnssec";
+
+        case STATUS_NTA:
+                return "negativeTrustAnchors";
+
+        default:
+                assert_not_reached();
+        }
+}
+
+static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        sd_json_variant *w;
+        const char *field;
+        int r;
+
+        assert(configuration);
+
+        field = status_mode_to_json_field(mode);
+        if (!field)
+                /* Nothing to filter for this mode. */
+                return 0;
+
+        JSON_VARIANT_ARRAY_FOREACH(w, *configuration) {
+                /* Always include identifier fields like ifname or delegate, and include the requested
+                 * field even if it is empty in the configuration. */
+                r = sd_json_variant_append_arraybo(
+                                &v,
+                                JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")),
+                                JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")),
+                                JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")),
+                                SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field)));
+                if (r < 0)
+                        return r;
+        }
+
+        JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v));
+        return 0;
+}
+
+static int status_json_filter_links(sd_json_variant **configuration, char **links) {
+        _cleanup_set_free_ Set *links_by_index = NULL;
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        sd_json_variant *w;
+        int r;
+
+        assert(configuration);
+
+        if (links)
+                STRV_FOREACH(ifname, links) {
+                        int ifindex = rtnl_resolve_interface_or_warn(/* rtnl= */ NULL, *ifname);
+
+                        if (ifindex < 0)
+                                return ifindex;
+
+                        r = set_ensure_put(&links_by_index, NULL, INT_TO_PTR(ifindex));
+                        if (r < 0)
+                                return r;
+                }
+
+        JSON_VARIANT_ARRAY_FOREACH(w, *configuration) {
+                int ifindex = sd_json_variant_unsigned(sd_json_variant_by_key(w, "ifindex"));
+
+                if (links_by_index) {
+                        if (ifindex <= 0)
+                                /* Possibly invalid, but most likely unset because this is global
+                                 * or delegate configuration. */
+                                continue;
+
+                        if (!set_contains(links_by_index, INT_TO_PTR(ifindex)))
+                                continue;
+
+                } else if (ifindex == LOOPBACK_IFINDEX)
+                        /* By default, exclude the loopback interface. */
+                        continue;
+
+                r = sd_json_variant_append_array(&v, w);
+                if (r < 0)
+                        return r;
+        }
+
+        JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v));
+        return 0;
+}
+
+static int varlink_dump_dns_configuration(sd_json_variant **ret) {
+        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL;
+        sd_json_variant *v;
+        int r;
+
+        assert(ret);
+
+        r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve");
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m");
+
+        r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply);
+        if (r < 0)
+                return r;
+
+        v = sd_json_variant_by_key(reply, "configuration");
+
+        if (!sd_json_variant_is_array(v))
+                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key.");
+
+        TAKE_PTR(reply);
+        *ret = sd_json_variant_ref(v);
+        return 0;
+}
+
+static int status_json(StatusMode mode, char **links) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL;
+        int r;
+
+        r = varlink_dump_dns_configuration(&configuration);
+        if (r < 0)
+                return r;
+
+        r = status_json_filter_links(&configuration, links);
+        if (r < 0)
+                return log_error_errno(r, "Failed to filter configuration JSON links: %m");
+
+        r = status_json_filter_fields(&configuration, mode);
+        if (r < 0)
+                return log_error_errno(r, "Failed to filter configuration JSON fields: %m");
+
+        return sd_json_variant_dump(configuration, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL);
+}
+
 static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode mode, bool *empty_line) {
         static const struct bus_properties_map property_map[] = {
                 { "ScopesMask",                 "t",        NULL,                           offsetof(LinkInfo, scopes_mask)      },
@@ -1828,6 +1984,9 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode
                 name = ifname;
         }
 
+        if (sd_json_format_enabled(arg_json_format_flags))
+                return status_json(mode, STRV_MAKE(name));
+
         xsprintf(ifi, "%i", ifindex);
         r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifi, &p);
         if (r < 0)
@@ -2423,6 +2582,9 @@ static int status_all(sd_bus *bus, StatusMode mode) {
 
         assert(bus);
 
+        if (sd_json_format_enabled(arg_json_format_flags))
+                return status_json(mode, /* links= */ NULL);
+
         r = status_global(bus, mode, &empty_line);
         if (r < 0)
                 return r;
@@ -2444,6 +2606,9 @@ static int verb_status(int argc, char **argv, void *userdata) {
         bool empty_line = false;
         int r, ret = 0;
 
+        if (sd_json_format_enabled(arg_json_format_flags))
+                return status_json(STATUS_ALL, argc > 1 ? strv_skip(argv, 1) : NULL);
+
         r = acquire_bus(&bus);
         if (r < 0)
                 return r;