diff --git a/man/networkctl.xml b/man/networkctl.xml
index ee204a60c5..19095164ce 100644
--- a/man/networkctl.xml
+++ b/man/networkctl.xml
@@ -367,6 +367,7 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR)
+
diff --git a/shell-completion/bash/hostnamectl b/shell-completion/bash/hostnamectl
index 5c8d3975d7..af4339111e 100644
--- a/shell-completion/bash/hostnamectl
+++ b/shell-completion/bash/hostnamectl
@@ -25,11 +25,38 @@ __contains_word () {
done
}
+__get_machines() {
+ local a b
+ machinectl list --full --no-legend --no-pager 2>/dev/null |
+ { while read a b; do echo " $a"; done; };
+}
+
_hostnamectl() {
local i verb comps
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
- local OPTS='-h --help --version --transient --static --pretty
- --no-ask-password -H --host -M --machine'
+ local -A OPTS=(
+ [STANDALONE]='-h --help --version --transient --static --pretty --no-ask-password'
+ [ARG]='-H --host -M --machine --json'
+ )
+
+ if __contains_word "$prev" ${OPTS[ARG]} ${OPTS[ARGUNKNOWN]}; then
+ case $prev in
+ --host|-H)
+ comps=$(compgen -A hostname)
+ ;;
+ --machine|-M)
+ comps=$( __get_machines )
+ ;;
+ --json)
+ comps=$( hostnamectl --json=help 2>/dev/null )
+ ;;
+ *)
+ return 0
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+ fi
if [[ $cur = -* ]]; then
COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
diff --git a/shell-completion/bash/networkctl b/shell-completion/bash/networkctl
index ab2a5f7015..1e4d0a2aae 100644
--- a/shell-completion/bash/networkctl
+++ b/shell-completion/bash/networkctl
@@ -33,7 +33,7 @@ _networkctl() {
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
local -A OPTS=(
[STANDALONE]='-a --all -h --help --version --no-pager --no-legend -s --stats -l --full'
- [ARG]='-n --lines'
+ [ARG]='-n --lines --json'
)
local -A VERBS=(
@@ -51,6 +51,19 @@ _networkctl() {
fi
done
+ if __contains_word "$prev" ${OPTS[ARG]}; then
+ case $prev in
+ --json)
+ comps=$(networkctl --json=help | sort 2>/dev/null)
+ ;;
+ *)
+ return 0
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+ fi
+
if [[ "$cur" = -* ]]; then
COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
return 0
diff --git a/shell-completion/zsh/_hostnamectl b/shell-completion/zsh/_hostnamectl
index 2a6bf4d125..2f1f51005d 100644
--- a/shell-completion/zsh/_hostnamectl
+++ b/shell-completion/zsh/_hostnamectl
@@ -75,6 +75,13 @@ _hostnamectl_commands() {
fi
}
+(( $+functions[_hostnamectl_get_json] )) || _hostnamectl_get_json()
+{
+ local -a _json_forms
+ _json_forms=( $(hostnamectl --json=help 2>/dev/null) )
+ _values 'format' $_json_forms
+}
+
_arguments -s \
{-h,--help}'[Show this help]' \
'--version[Show package version]' \
@@ -84,4 +91,5 @@ _arguments -s \
'--no-ask-password[Do not prompt for password]' \
{-H+,--host=}'[Operate on remote host]:userathost:_sd_hosts_or_user_at_host' \
{-M+,--machine=}'[Operate on local container]:machines:_sd_machines' \
+ '--json[Shows output formatted as JSON]:format:_hostnamectl_get_json' \
'*::hostnamectl commands:_hostnamectl_commands'
diff --git a/shell-completion/zsh/_networkctl b/shell-completion/zsh/_networkctl
index 165c5411be..273e7d94a8 100644
--- a/shell-completion/zsh/_networkctl
+++ b/shell-completion/zsh/_networkctl
@@ -39,10 +39,18 @@
fi
}
+(( $+functions[_networkctl_get_json] )) || _networkctl_get_json()
+{
+ local -a _json_forms
+ _json_forms=( $(networkctl --json=help 2>/dev/null) )
+ _values 'format' $_json_forms
+}
+
_arguments \
{-a,--all}'[Show all links with status]' \
'--no-pager[Do not pipe output into a pager]' \
'--no-legend[Do not print the column headers]' \
{-h,--help}'[Show this help]' \
'--version[Show package version]' \
+ '--json[Shows output formatted as JSON]:format:_networkctl_get_json' \
'*::networkctl commands:_networkctl_commands'
diff --git a/src/libsystemd/sd-network/network-util.c b/src/libsystemd/sd-network/network-util.c
index c1d3a9f6a2..4570098388 100644
--- a/src/libsystemd/sd-network/network-util.c
+++ b/src/libsystemd/sd-network/network-util.c
@@ -119,24 +119,31 @@ int parse_operational_state_range(const char *str, LinkOperationalStateRange *ou
return 0;
}
-char *link_get_type_string(sd_device *device, unsigned short iftype) {
+int link_get_type_string(sd_device *device, unsigned short iftype, char **ret) {
const char *t;
char *p;
if (device &&
sd_device_get_devtype(device, &t) >= 0 &&
- !isempty(t))
- return strdup(t);
+ !isempty(t)) {
+ p = strdup(t);
+ if (!p)
+ return -ENOMEM;
+
+ *ret = p;
+ return 0;
+ }
t = arphrd_to_name(iftype);
if (!t)
- return NULL;
+ return -ENOENT;
p = strdup(t);
if (!p)
- return NULL;
+ return -ENOMEM;
- return ascii_strlower(p);
+ *ret = ascii_strlower(p);
+ return 0;
}
const char *net_get_name_persistent(sd_device *device) {
diff --git a/src/libsystemd/sd-network/network-util.h b/src/libsystemd/sd-network/network-util.h
index 3bd10c42de..d33f42cfb0 100644
--- a/src/libsystemd/sd-network/network-util.h
+++ b/src/libsystemd/sd-network/network-util.h
@@ -76,6 +76,6 @@ typedef struct LinkOperationalStateRange {
int parse_operational_state_range(const char *str, LinkOperationalStateRange *out);
-char *link_get_type_string(sd_device *device, unsigned short iftype);
+int link_get_type_string(sd_device *device, unsigned short iftype, char **ret);
int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *result);
const char *net_get_name_persistent(sd_device *device);
diff --git a/src/network/meson.build b/src/network/meson.build
index a8b9232e64..7b6a4b7c7a 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -79,6 +79,8 @@ sources = files('''
networkd-ipv4ll.h
networkd-ipv6-proxy-ndp.c
networkd-ipv6-proxy-ndp.h
+ networkd-json.c
+ networkd-json.h
networkd-link-bus.c
networkd-link-bus.h
networkd-link.c
diff --git a/src/network/networkctl.c b/src/network/networkctl.c
index 52abcddd64..7953a52b9d 100644
--- a/src/network/networkctl.c
+++ b/src/network/networkctl.c
@@ -46,6 +46,7 @@
#include "network-internal.h"
#include "network-util.h"
#include "pager.h"
+#include "parse-argument.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "set.h"
@@ -75,6 +76,110 @@ static bool arg_all = false;
static bool arg_stats = false;
static bool arg_full = false;
static unsigned arg_lines = 10;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
+
+static int get_description(JsonVariant **ret) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ const char *text = NULL;
+ int r;
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect system bus: %m");
+
+ r = bus_call_method(bus, bus_network_mgr, "Describe", &error, &reply, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get description: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "s", &text);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = json_parse(text, 0, ret, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse JSON: %m");
+
+ return 0;
+}
+
+static int dump_manager_description(void) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ int r;
+
+ r = get_description(&v);
+ if (r < 0)
+ return r;
+
+ json_variant_dump(v, arg_json_format_flags, NULL, NULL);
+ return 0;
+}
+
+static int dump_link_description(char **patterns) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ bool *matched_patterns = NULL;
+ JsonVariant *i;
+ size_t c = 0;
+ int r;
+
+ r = get_description(&v);
+ if (r < 0)
+ return r;
+
+ matched_patterns = new0(bool, strv_length(patterns));
+ if (!matched_patterns)
+ return log_oom();
+
+ JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(v, "Interfaces")) {
+ char ifindex_str[DECIMAL_STR_MAX(intmax_t)];
+ const char *name;
+ intmax_t index;
+ size_t pos;
+
+ name = json_variant_string(json_variant_by_key(i, "Name"));
+ index = json_variant_integer(json_variant_by_key(i, "Index"));
+ xsprintf(ifindex_str, "%ji", index);
+
+ if (!strv_fnmatch_full(patterns, ifindex_str, 0, &pos) &&
+ !strv_fnmatch_full(patterns, name, 0, &pos)) {
+ bool match = false;
+ JsonVariant *a;
+
+ JSON_VARIANT_ARRAY_FOREACH(a, json_variant_by_key(i, "AlternativeNames"))
+ if (strv_fnmatch_full(patterns, json_variant_string(a), 0, &pos)) {
+ match = true;
+ break;
+ }
+
+ if (!match)
+ continue;
+ }
+
+ matched_patterns[pos] = true;
+ json_variant_dump(i, arg_json_format_flags, NULL, NULL);
+ c++;
+ }
+
+ /* Look if we matched all our arguments that are not globs. It is OK for a glob to match
+ * nothing, but not for an exact argument. */
+ for (size_t pos = 0; pos < strv_length(patterns); pos++) {
+ if (matched_patterns[pos])
+ continue;
+
+ if (string_is_glob(patterns[pos]))
+ log_debug("Pattern \"%s\" doesn't match any interface, ignoring.",
+ patterns[pos]);
+ else
+ return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
+ "Interface \"%s\" not found.", patterns[pos]);
+ }
+
+ if (c == 0)
+ log_warning("No interfaces matched.");
+
+ return 0;
+}
static void operational_state_to_color(const char *name, const char *state, const char **on, const char **off) {
if (STRPTR_IN_SET(state, "routable", "enslaved") ||
@@ -674,6 +779,13 @@ static int list_links(int argc, char *argv[], void *userdata) {
TableCell *cell;
int c, r;
+ if (arg_json_format_flags != JSON_FORMAT_OFF) {
+ if (arg_all || argc <= 1)
+ return dump_manager_description();
+ else
+ return dump_link_description(strv_skip(argv, 1));
+ }
+
r = sd_netlink_open(&rtnl);
if (r < 0)
return log_error_errno(r, "Failed to connect to netlink: %m");
@@ -692,6 +804,8 @@ static int list_links(int argc, char *argv[], void *userdata) {
table_set_width(table, 0);
table_set_header(table, arg_legend);
+ if (table_set_empty_string(table, "n/a") < 0)
+ return log_oom();
assert_se(cell = table_get_cell(table, 0, 0));
(void) table_set_minimum_width(table, cell, 3);
@@ -715,15 +829,17 @@ static int list_links(int argc, char *argv[], void *userdata) {
setup_state = strdup("unmanaged");
setup_state_to_color(setup_state, &on_color_setup, NULL);
- t = link_get_type_string(links[i].sd_device, links[i].iftype);
+ r = link_get_type_string(links[i].sd_device, links[i].iftype, &t);
+ if (r == -ENOMEM)
+ return log_oom();
r = table_add_many(table,
TABLE_INT, links[i].ifindex,
TABLE_STRING, links[i].name,
- TABLE_STRING, strna(t),
- TABLE_STRING, strna(operational_state),
+ TABLE_STRING, t,
+ TABLE_STRING, operational_state,
TABLE_SET_COLOR, on_color_operational,
- TABLE_STRING, strna(setup_state),
+ TABLE_STRING, setup_state,
TABLE_SET_COLOR, on_color_setup);
if (r < 0)
return table_log_add_error(r);
@@ -1436,7 +1552,9 @@ static int link_status_one(
(void) sd_device_get_property_value(info->sd_device, "ID_MODEL", &model);
}
- t = link_get_type_string(info->sd_device, info->iftype);
+ r = link_get_type_string(info->sd_device, info->iftype, &t);
+ if (r == -ENOMEM)
+ return log_oom();
(void) sd_network_link_get_network_file(info->ifindex, &network);
@@ -2232,6 +2350,13 @@ static int link_status(int argc, char *argv[], void *userdata) {
_cleanup_(link_info_array_freep) LinkInfo *links = NULL;
int r, c;
+ if (arg_json_format_flags != JSON_FORMAT_OFF) {
+ if (arg_all || argc <= 1)
+ return dump_manager_description();
+ else
+ return dump_link_description(strv_skip(argv, 1));
+ }
+
(void) pager_open(arg_pager_flags);
r = sd_bus_open_system(&bus);
@@ -2722,6 +2847,8 @@ static int help(void) {
" -s --stats Show detailed link statics\n"
" -l --full Do not ellipsize output\n"
" -n --lines=INTEGER Number of journal entries to show\n"
+ " --json=pretty|short|off\n"
+ " Generate JSON output\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@@ -2737,6 +2864,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_NO_LEGEND,
+ ARG_JSON,
};
static const struct option options[] = {
@@ -2748,10 +2876,11 @@ static int parse_argv(int argc, char *argv[]) {
{ "stats", no_argument, NULL, 's' },
{ "full", no_argument, NULL, 'l' },
{ "lines", required_argument, NULL, 'n' },
+ { "json", required_argument, NULL, ARG_JSON },
{}
};
- int c;
+ int c, r;
assert(argc >= 0);
assert(argv);
@@ -2792,6 +2921,12 @@ static int parse_argv(int argc, char *argv[]) {
"Failed to parse lines '%s'", optarg);
break;
+ case ARG_JSON:
+ r = parse_json_argument(optarg, &arg_json_format_flags);
+ if (r <= 0)
+ return r;
+ break;
+
case '?':
return -EINVAL;
diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c
new file mode 100644
index 0000000000..7a7fc24ea6
--- /dev/null
+++ b/src/network/networkd-json.c
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "network-util.h"
+#include "networkd-json.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-network.h"
+#include "sort-util.h"
+
+static int network_build_json(Network *network, JsonVariant **ret) {
+ assert(network);
+ assert(ret);
+
+ return json_build(ret, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("NetworkFile", JSON_BUILD_STRING(network->filename))));
+}
+
+static int device_build_json(sd_device *device, JsonVariant **ret) {
+ const char *link = NULL, *path = NULL, *vendor = NULL, *model = NULL;
+
+ assert(device);
+ assert(ret);
+
+ (void) sd_device_get_property_value(device, "ID_NET_LINK_FILE", &link);
+ (void) sd_device_get_property_value(device, "ID_PATH", &path);
+
+ if (sd_device_get_property_value(device, "ID_VENDOR_FROM_DATABASE", &vendor) < 0)
+ (void) sd_device_get_property_value(device, "ID_VENDOR", &vendor);
+
+ if (sd_device_get_property_value(device, "ID_MODEL_FROM_DATABASE", &model) < 0)
+ (void) sd_device_get_property_value(device, "ID_MODEL", &model);
+
+ return json_build(ret, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("LinkFile", JSON_BUILD_STRING(link)),
+ JSON_BUILD_PAIR("Path", JSON_BUILD_STRING(path)),
+ JSON_BUILD_PAIR("Vendor", JSON_BUILD_STRING(vendor)),
+ JSON_BUILD_PAIR("Model", JSON_BUILD_STRING(model))));
+}
+
+int link_build_json(Link *link, JsonVariant **ret) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ char *type = NULL;
+ int r;
+
+ assert(link);
+ assert(ret);
+
+ r = link_get_type_string(link->sd_device, link->iftype, &type);
+ if (r == -ENOMEM)
+ return r;
+
+ r = json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("Index", JSON_BUILD_INTEGER(link->ifindex)),
+ JSON_BUILD_PAIR("Name", JSON_BUILD_STRING(link->ifname)),
+ JSON_BUILD_PAIR("AlternativeNames", JSON_BUILD_STRV(link->alternative_names)),
+ JSON_BUILD_PAIR("Type", JSON_BUILD_STRING(type)),
+ JSON_BUILD_PAIR("Driver", JSON_BUILD_STRING(link->driver)),
+ JSON_BUILD_PAIR("SetupState", JSON_BUILD_STRING(link_state_to_string(link->state))),
+ JSON_BUILD_PAIR("OperationalState", JSON_BUILD_STRING(link_operstate_to_string(link->operstate))),
+ JSON_BUILD_PAIR("CarrierState", JSON_BUILD_STRING(link_carrier_state_to_string(link->carrier_state))),
+ JSON_BUILD_PAIR("AddressState", JSON_BUILD_STRING(link_address_state_to_string(link->address_state))),
+ JSON_BUILD_PAIR("IPv4AddressState", JSON_BUILD_STRING(link_address_state_to_string(link->ipv4_address_state))),
+ JSON_BUILD_PAIR("IPv6AddressState", JSON_BUILD_STRING(link_address_state_to_string(link->ipv6_address_state)))));
+ if (r < 0)
+ return r;
+
+ if (link->network) {
+ _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+
+ r = network_build_json(link->network, &w);
+ if (r < 0)
+ return r;
+
+ r = json_variant_merge(&v, w);
+ if (r < 0)
+ return r;
+ }
+
+ if (link->sd_device) {
+ _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+
+ r = device_build_json(link->sd_device, &w);
+ if (r < 0)
+ return r;
+
+ r = json_variant_merge(&v, w);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(v);
+ return 0;
+}
+
+static int link_json_compare(JsonVariant * const *a, JsonVariant * const *b) {
+ intmax_t index_a, index_b;
+
+ assert(a && *a);
+ assert(b && *b);
+
+ index_a = json_variant_integer(json_variant_by_key(*a, "Index"));
+ index_b = json_variant_integer(json_variant_by_key(*b, "Index"));
+
+ return CMP(index_a, index_b);
+}
+
+int manager_build_json(Manager *manager, JsonVariant **ret) {
+ JsonVariant **elements;
+ Link *link;
+ size_t n = 0;
+ int r;
+
+ assert(manager);
+ assert(ret);
+
+ elements = new(JsonVariant*, hashmap_size(manager->links));
+ if (!elements)
+ return -ENOMEM;
+
+ HASHMAP_FOREACH(link, manager->links) {
+ r = link_build_json(link, elements + n);
+ if (r < 0)
+ goto finalize;
+ n++;
+ }
+
+ typesafe_qsort(elements, n, link_json_compare);
+
+ r = json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("Interfaces", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+
+finalize:
+ json_variant_unref_many(elements, n);
+ free(elements);
+ return r;
+}
diff --git a/src/network/networkd-json.h b/src/network/networkd-json.h
new file mode 100644
index 0000000000..25018fa097
--- /dev/null
+++ b/src/network/networkd-json.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "json.h"
+
+typedef struct Link Link;
+typedef struct Manager Manager;
+
+int link_build_json(Link *link, JsonVariant **ret);
+int manager_build_json(Manager *manager, JsonVariant **ret);
diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c
index a999b05845..f2f47c52b0 100644
--- a/src/network/networkd-link-bus.c
+++ b/src/network/networkd-link-bus.c
@@ -10,6 +10,7 @@
#include "bus-message-util.h"
#include "bus-polkit.h"
#include "dns-domain.h"
+#include "networkd-json.h"
#include "networkd-link-bus.h"
#include "networkd-link.h"
#include "networkd-manager.h"
@@ -678,6 +679,35 @@ int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_
return sd_bus_reply_method_return(message, NULL);
}
+int bus_link_method_describe(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ char *text = NULL;
+ Link *link = userdata;
+ int r;
+
+ assert(message);
+ assert(link);
+
+ r = link_build_json(link, &v);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to build JSON data: %m");
+
+ r = json_variant_format(v, 0, &text);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to format JSON data: %m");
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "s", text);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
const sd_bus_vtable link_vtable[] = {
SD_BUS_VTABLE_START(0),
@@ -764,6 +794,11 @@ const sd_bus_vtable link_vtable[] = {
SD_BUS_NO_RESULT,
bus_link_method_reconfigure,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Describe",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", json),
+ bus_link_method_describe,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
diff --git a/src/network/networkd-link-bus.h b/src/network/networkd-link-bus.h
index 45594dfac9..ddc3fcb39d 100644
--- a/src/network/networkd-link-bus.h
+++ b/src/network/networkd-link-bus.h
@@ -34,3 +34,4 @@ int bus_link_method_revert_dns(sd_bus_message *message, void *userdata, sd_bus_e
int bus_link_method_renew(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_link_method_force_renew(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_link_method_describe(sd_bus_message *message, void *userdata, sd_bus_error *error);
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 152ac05e7c..9d30e16b0a 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -2226,6 +2226,59 @@ static int link_configure_continue(Link *link) {
return link_enter_join_netdev(link);
}
+static int link_get_network(Link *link, Network **ret) {
+ Network *network;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(ret);
+
+ ORDERED_HASHMAP_FOREACH(network, link->manager->networks) {
+ bool warn = false;
+
+ r = net_match_config(
+ &network->match,
+ link->sd_device,
+ &link->hw_addr.addr.ether,
+ &link->permanent_mac,
+ link->driver,
+ link->iftype,
+ link->ifname,
+ link->alternative_names,
+ link->wlan_iftype,
+ link->ssid,
+ &link->bssid);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (network->match.ifname && link->sd_device) {
+ uint8_t name_assign_type = NET_NAME_UNKNOWN;
+ const char *attr;
+
+ if (sd_device_get_sysattr_value(link->sd_device, "name_assign_type", &attr) >= 0)
+ (void) safe_atou8(attr, &name_assign_type);
+
+ warn = name_assign_type == NET_NAME_ENUM;
+ }
+
+ log_link_full(link, warn ? LOG_WARNING : LOG_DEBUG,
+ "found matching network '%s'%s.",
+ network->filename,
+ warn ? ", based on potentially unpredictable interface name" : "");
+
+ if (network->unmanaged)
+ return -ENOENT;
+
+ *ret = network;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
static int link_reconfigure_internal(Link *link, sd_netlink_message *m, bool force) {
_cleanup_strv_free_ char **s = NULL;
Network *network;
@@ -2243,17 +2296,12 @@ static int link_reconfigure_internal(Link *link, sd_netlink_message *m, bool for
strv_free_and_replace(link->alternative_names, s);
- r = network_get(link->manager, link->iftype, link->sd_device,
- link->ifname, link->alternative_names, link->driver,
- &link->hw_addr.addr.ether, &link->permanent_mac,
- link->wlan_iftype, link->ssid, &link->bssid, &network);
+ r = link_get_network(link, &network);
if (r == -ENOENT) {
link_enter_unmanaged(link);
return 0;
- } else if (r == 0 && network->unmanaged) {
- link_enter_unmanaged(link);
- return 0;
- } else if (r < 0)
+ }
+ if (r < 0)
return r;
if (link->network == network && !force)
@@ -2377,17 +2425,12 @@ static int link_initialized_and_synced(Link *link) {
if (r < 0)
return r;
- r = network_get(link->manager, link->iftype, link->sd_device,
- link->ifname, link->alternative_names, link->driver,
- &link->hw_addr.addr.ether, &link->permanent_mac,
- link->wlan_iftype, link->ssid, &link->bssid, &network);
+ r = link_get_network(link, &network);
if (r == -ENOENT) {
link_enter_unmanaged(link);
return 0;
- } else if (r == 0 && network->unmanaged) {
- link_enter_unmanaged(link);
- return 0;
- } else if (r < 0)
+ }
+ if (r < 0)
return r;
if (link->flags & IFF_LOOPBACK) {
diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c
index 0acaeb2bed..f66d7d1612 100644
--- a/src/network/networkd-manager-bus.c
+++ b/src/network/networkd-manager-bus.c
@@ -8,6 +8,7 @@
#include "bus-common-errors.h"
#include "bus-message-util.h"
#include "bus-polkit.h"
+#include "networkd-json.h"
#include "networkd-link-bus.h"
#include "networkd-link.h"
#include "networkd-manager-bus.h"
@@ -229,6 +230,39 @@ static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_err
return sd_bus_reply_method_return(message, NULL);
}
+static int bus_method_describe_link(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ return call_link_method(userdata, message, bus_link_method_describe, error);
+}
+
+static int bus_method_describe(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ char *text = NULL;
+ Manager *manager = userdata;
+ int r;
+
+ assert(message);
+ assert(manager);
+
+ r = manager_build_json(manager, &v);
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON data: %m");
+
+ r = json_variant_format(v, 0, &text);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format JSON data: %m");
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "s", text);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
const sd_bus_vtable manager_vtable[] = {
SD_BUS_VTABLE_START(0),
@@ -333,6 +367,16 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_NO_RESULT,
bus_method_reload,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("DescribeLink",
+ SD_BUS_ARGS("i", ifindex),
+ SD_BUS_RESULT("s", json),
+ bus_method_describe_link,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Describe",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", json),
+ bus_method_describe,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 8b236342b5..482c31064f 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -646,43 +646,6 @@ int network_get_by_name(Manager *manager, const char *name, Network **ret) {
return 0;
}
-int network_get(Manager *manager, unsigned short iftype, sd_device *device,
- const char *ifname, char * const *alternative_names, const char *driver,
- const struct ether_addr *mac, const struct ether_addr *permanent_mac,
- enum nl80211_iftype wlan_iftype, const char *ssid, const struct ether_addr *bssid,
- Network **ret) {
- Network *network;
-
- assert(manager);
- assert(ret);
-
- ORDERED_HASHMAP_FOREACH(network, manager->networks)
- if (net_match_config(&network->match, device, mac, permanent_mac, driver, iftype,
- ifname, alternative_names, wlan_iftype, ssid, bssid)) {
- if (network->match.ifname && device) {
- const char *attr;
- uint8_t name_assign_type = NET_NAME_UNKNOWN;
-
- if (sd_device_get_sysattr_value(device, "name_assign_type", &attr) >= 0)
- (void) safe_atou8(attr, &name_assign_type);
-
- if (name_assign_type == NET_NAME_ENUM)
- log_warning("%s: found matching network '%s', based on potentially unpredictable ifname",
- ifname, network->filename);
- else
- log_debug("%s: found matching network '%s'", ifname, network->filename);
- } else
- log_debug("%s: found matching network '%s'", ifname, network->filename);
-
- *ret = network;
- return 0;
- }
-
- *ret = NULL;
-
- return -ENOENT;
-}
-
bool network_has_static_ipv6_configurations(Network *network) {
Address *address;
Route *route;
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index c094065a83..e27a27cd02 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -341,11 +341,6 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
int network_verify(Network *network);
int network_get_by_name(Manager *manager, const char *name, Network **ret);
-int network_get(Manager *manager, unsigned short iftype, sd_device *device,
- const char *ifname, char * const *alternative_names, const char *driver,
- const struct ether_addr *mac, const struct ether_addr *permanent_mac,
- enum nl80211_iftype wlan_iftype, const char *ssid, const struct ether_addr *bssid,
- Network **ret);
void network_apply_anonymize_if_set(Network *network);
bool network_has_static_ipv6_configurations(Network *network);
diff --git a/src/network/test-network.c b/src/network/test-network.c
index 25ff3a33a8..09e7b99d23 100644
--- a/src/network/test-network.c
+++ b/src/network/test-network.c
@@ -168,23 +168,6 @@ static int test_load_config(Manager *manager) {
return 0;
}
-static void test_network_get(Manager *manager, sd_device *loopback) {
- Network *network;
- const struct ether_addr mac = ETHER_ADDR_NULL;
- int r;
-
- /* Let's hope that the test machine does not have a .network file that applies to loopback deviceā¦
- * But it is still possible, so let's allow that case too. */
- r = network_get(manager, 0, loopback, "lo", NULL, NULL, &mac, &mac, 0, NULL, NULL, &network);
- if (r == -ENOENT)
- /* The expected case */
- assert_se(!network);
- else if (r >= 0)
- assert_se(network);
- else
- assert_not_reached("bad error!");
-}
-
static void test_address_equality(void) {
_cleanup_(address_freep) Address *a1 = NULL, *a2 = NULL;
@@ -277,8 +260,7 @@ static void test_dhcp_hostname_shorten_overlong(void) {
int main(void) {
_cleanup_(manager_freep) Manager *manager = NULL;
- _cleanup_(sd_device_unrefp) sd_device *loopback = NULL;
- int ifindex, r;
+ int r;
test_setup_logging(LOG_INFO);
@@ -293,15 +275,9 @@ int main(void) {
r = test_load_config(manager);
if (r == -EPERM)
- return log_tests_skipped("Cannot load configuration");
- assert_se(r == 0);
-
- assert_se(sd_device_new_from_syspath(&loopback, "/sys/class/net/lo") >= 0);
- assert_se(loopback);
- assert_se(sd_device_get_ifindex(loopback, &ifindex) >= 0);
- assert_se(ifindex == 1);
-
- test_network_get(manager, loopback);
+ log_debug("Cannot load configuration, ignoring.");
+ else
+ assert_se(r == 0);
assert_se(manager_enumerate(manager) >= 0);
return 0;
diff --git a/src/shared/json.h b/src/shared/json.h
index 148701a54f..c679f8f1ab 100644
--- a/src/shared/json.h
+++ b/src/shared/json.h
@@ -146,22 +146,26 @@ struct json_variant_foreach_state {
size_t idx;
};
+#define _JSON_VARIANT_ARRAY_FOREACH(i, v, state) \
+ for (struct json_variant_foreach_state state = { (v), 0 }; \
+ json_variant_is_array(state.variant) && \
+ state.idx < json_variant_elements(state.variant) && \
+ ({ i = json_variant_by_index(state.variant, state.idx); \
+ true; }); \
+ state.idx++)
#define JSON_VARIANT_ARRAY_FOREACH(i, v) \
- for (struct json_variant_foreach_state _state = { (v), 0 }; \
- json_variant_is_array(_state.variant) && \
- _state.idx < json_variant_elements(_state.variant) && \
- ({ i = json_variant_by_index(_state.variant, _state.idx); \
- true; }); \
- _state.idx++)
+ _JSON_VARIANT_ARRAY_FOREACH(i, v, UNIQ_T(state, UNIQ))
-#define JSON_VARIANT_OBJECT_FOREACH(k, e, v) \
- for (struct json_variant_foreach_state _state = { (v), 0 }; \
- json_variant_is_object(_state.variant) && \
- _state.idx < json_variant_elements(_state.variant) && \
- ({ k = json_variant_string(json_variant_by_index(_state.variant, _state.idx)); \
- e = json_variant_by_index(_state.variant, _state.idx + 1); \
+#define _JSON_VARIANT_OBJECT_FOREACH(k, e, v, state) \
+ for (struct json_variant_foreach_state state = { (v), 0 }; \
+ json_variant_is_object(state.variant) && \
+ state.idx < json_variant_elements(state.variant) && \
+ ({ k = json_variant_string(json_variant_by_index(state.variant, state.idx)); \
+ e = json_variant_by_index(state.variant, state.idx + 1); \
true; }); \
- _state.idx += 2)
+ state.idx += 2)
+#define JSON_VARIANT_OBJECT_FOREACH(k, e, v) \
+ _JSON_VARIANT_OBJECT_FOREACH(k, e, v, UNIQ_T(state, UNIQ))
int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column);
diff --git a/src/shared/net-condition.c b/src/shared/net-condition.c
index 2479a5672c..4742f37447 100644
--- a/src/shared/net-condition.c
+++ b/src/shared/net-condition.c
@@ -134,7 +134,7 @@ static const char *const wifi_iftype_table[NL80211_IFTYPE_MAX+1] = {
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(wifi_iftype, enum nl80211_iftype);
-bool net_match_config(
+int net_match_config(
const NetMatch *match,
sd_device *device,
const struct ether_addr *mac,
@@ -149,10 +149,13 @@ bool net_match_config(
_cleanup_free_ char *iftype_str = NULL;
const char *path = NULL;
+ int r;
assert(match);
- iftype_str = link_get_type_string(device, iftype);
+ r = link_get_type_string(device, iftype, &iftype_str);
+ if (r == -ENOMEM)
+ return r;
if (device) {
const char *mac_str;
diff --git a/src/shared/net-condition.h b/src/shared/net-condition.h
index 8d85fc8a7f..d61537e5e3 100644
--- a/src/shared/net-condition.h
+++ b/src/shared/net-condition.h
@@ -26,7 +26,7 @@ typedef struct NetMatch {
void net_match_clear(NetMatch *match);
bool net_match_is_empty(const NetMatch *match);
-bool net_match_config(
+int net_match_config(
const NetMatch *match,
sd_device *device,
const struct ether_addr *mac,
diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c
index 87afe8383a..3685c9d228 100644
--- a/src/udev/net/link-config.c
+++ b/src/udev/net/link-config.c
@@ -273,16 +273,20 @@ int link_config_get(link_config_ctx *ctx, sd_device *device, link_config **ret)
(void) link_unsigned_attribute(device, "name_assign_type", &name_assign_type);
LIST_FOREACH(links, link, ctx->links) {
- if (net_match_config(&link->match, device, NULL, &permanent_mac, NULL, iftype, NULL, NULL, 0, NULL, NULL)) {
- if (link->match.ifname && !strv_contains(link->match.ifname, "*") && name_assign_type == NET_NAME_ENUM)
- log_device_warning(device, "Config file %s is applied to device based on potentially unpredictable interface name.",
- link->filename);
- else
- log_device_debug(device, "Config file %s is applied", link->filename);
+ r = net_match_config(&link->match, device, NULL, &permanent_mac, NULL, iftype, NULL, NULL, 0, NULL, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
- *ret = link;
- return 0;
- }
+ if (link->match.ifname && !strv_contains(link->match.ifname, "*") && name_assign_type == NET_NAME_ENUM)
+ log_device_warning(device, "Config file %s is applied to device based on potentially unpredictable interface name.",
+ link->filename);
+ else
+ log_device_debug(device, "Config file %s is applied", link->filename);
+
+ *ret = link;
+ return 0;
}
return -ENOENT;