]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
networkctl: split networkctl.c into small pieces
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 21 Aug 2024 06:31:57 +0000 (15:31 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 21 Aug 2024 16:12:39 +0000 (01:12 +0900)
No functional change, just refactoring.

26 files changed:
src/network/meson.build
src/network/networkctl-address-label.c [new file with mode: 0644]
src/network/networkctl-address-label.h [new file with mode: 0644]
src/network/networkctl-config-file.c
src/network/networkctl-description.c [new file with mode: 0644]
src/network/networkctl-description.h [new file with mode: 0644]
src/network/networkctl-dump-util.c [new file with mode: 0644]
src/network/networkctl-dump-util.h [new file with mode: 0644]
src/network/networkctl-journal.c [new file with mode: 0644]
src/network/networkctl-journal.h [new file with mode: 0644]
src/network/networkctl-link-info.c [new file with mode: 0644]
src/network/networkctl-link-info.h [new file with mode: 0644]
src/network/networkctl-list.c [new file with mode: 0644]
src/network/networkctl-list.h [new file with mode: 0644]
src/network/networkctl-lldp.c [new file with mode: 0644]
src/network/networkctl-lldp.h [new file with mode: 0644]
src/network/networkctl-misc.c [new file with mode: 0644]
src/network/networkctl-misc.h [new file with mode: 0644]
src/network/networkctl-status-link.c [new file with mode: 0644]
src/network/networkctl-status-link.h [new file with mode: 0644]
src/network/networkctl-status-system.c [new file with mode: 0644]
src/network/networkctl-status-system.h [new file with mode: 0644]
src/network/networkctl-util.c [new file with mode: 0644]
src/network/networkctl-util.h [new file with mode: 0644]
src/network/networkctl.c
src/network/networkctl.h

index a983dff48bb30e2a746efff3c60a127cf44482fb..275542daa27f53a6e763b6fc087dc5d0481b08e2 100644 (file)
@@ -116,7 +116,18 @@ systemd_networkd_wait_online_sources = files(
 
 networkctl_sources = files(
         'networkctl.c',
-        'networkctl-config-file.c'
+        'networkctl-address-label.c',
+        'networkctl-config-file.c',
+        'networkctl-description.c',
+        'networkctl-dump-util.c',
+        'networkctl-journal.c',
+        'networkctl-link-info.c',
+        'networkctl-list.c',
+        'networkctl-lldp.c',
+        'networkctl-misc.c',
+        'networkctl-status-link.c',
+        'networkctl-status-system.c',
+        'networkctl-util.c',
 )
 
 network_generator_sources = files(
diff --git a/src/network/networkctl-address-label.c b/src/network/networkctl-address-label.c
new file mode 100644 (file)
index 0000000..cb7d3d7
--- /dev/null
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/if_addrlabel.h>
+
+#include "sd-netlink.h"
+
+#include "format-table.h"
+#include "in-addr-util.h"
+#include "networkctl.h"
+#include "networkctl-address-label.h"
+
+static int dump_address_labels(sd_netlink *rtnl) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        TableCell *cell;
+        int r;
+
+        assert(rtnl);
+
+        r = sd_rtnl_message_new_addrlabel(rtnl, &req, RTM_GETADDRLABEL, 0, AF_INET6);
+        if (r < 0)
+                return log_error_errno(r, "Could not allocate RTM_GETADDRLABEL message: %m");
+
+        r = sd_netlink_message_set_request_dump(req, true);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_call(rtnl, req, 0, &reply);
+        if (r < 0)
+                return r;
+
+        table = table_new("label", "prefix/prefixlen");
+        if (!table)
+                return log_oom();
+
+        if (arg_full)
+                table_set_width(table, 0);
+
+        r = table_set_sort(table, (size_t) 0);
+        if (r < 0)
+                return r;
+
+        assert_se(cell = table_get_cell(table, 0, 0));
+        (void) table_set_align_percent(table, cell, 100);
+        (void) table_set_ellipsize_percent(table, cell, 100);
+
+        assert_se(cell = table_get_cell(table, 0, 1));
+        (void) table_set_align_percent(table, cell, 100);
+
+        for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
+                struct in6_addr prefix;
+                uint8_t prefixlen;
+                uint32_t label;
+
+                r = sd_netlink_message_get_errno(m);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to get netlink message, ignoring: %m");
+                        continue;
+                }
+
+                r = sd_netlink_message_read_u32(m, IFAL_LABEL, &label);
+                if (r < 0 && r != -ENODATA) {
+                        log_error_errno(r, "Could not read IFAL_LABEL, ignoring: %m");
+                        continue;
+                }
+
+                r = sd_netlink_message_read_in6_addr(m, IFAL_ADDRESS, &prefix);
+                if (r < 0)
+                        continue;
+
+                r = sd_rtnl_message_addrlabel_get_prefixlen(m, &prefixlen);
+                if (r < 0)
+                        continue;
+
+                r = table_add_cell(table, NULL, TABLE_UINT32, &label);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                r = table_add_cell_stringf(table, NULL, "%s/%u", IN6_ADDR_TO_STRING(&prefix), prefixlen);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        r = table_print(table, NULL);
+        if (r < 0)
+                return table_log_print_error(r);
+
+        return 0;
+}
+
+int list_address_labels(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        int r;
+
+        r = sd_netlink_open(&rtnl);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to netlink: %m");
+
+        return dump_address_labels(rtnl);
+}
diff --git a/src/network/networkctl-address-label.h b/src/network/networkctl-address-label.h
new file mode 100644 (file)
index 0000000..eb3c722
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int list_address_labels(int argc, char *argv[], void *userdata);
index 1aa03619b728f8c6d4f23fa3c5edae08a7f2e5d2..1bfc185f7ccbe35327a4731b401b758683a3317f 100644 (file)
@@ -17,6 +17,7 @@
 #include "netlink-util.h"
 #include "networkctl.h"
 #include "networkctl-config-file.h"
+#include "networkctl-util.h"
 #include "pager.h"
 #include "path-lookup.h"
 #include "path-util.h"
diff --git a/src/network/networkctl-description.c b/src/network/networkctl-description.c
new file mode 100644 (file)
index 0000000..14ce55b
--- /dev/null
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "bus-util.h"
+#include "glob-util.h"
+#include "json-util.h"
+#include "networkctl.h"
+#include "networkctl-description.h"
+#include "networkctl-util.h"
+#include "stdio-util.h"
+#include "strv.h"
+
+static int get_description(sd_bus *bus, sd_json_variant **ret) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        const char *text;
+        int r;
+
+        assert(bus);
+        assert(ret);
+
+        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 = sd_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(sd_bus *bus) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        int r;
+
+        assert(bus);
+
+        r = get_description(bus, &v);
+        if (r < 0)
+                return r;
+
+        sd_json_variant_dump(v, arg_json_format_flags, NULL, NULL);
+        return 0;
+}
+
+static int dump_link_description(sd_bus *bus, char * const *patterns) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        _cleanup_free_ bool *matched_patterns = NULL;
+        sd_json_variant *i;
+        size_t c = 0;
+        int r;
+
+        assert(bus);
+        assert(patterns);
+
+        r = get_description(bus, &v);
+        if (r < 0)
+                return r;
+
+        matched_patterns = new0(bool, strv_length(patterns));
+        if (!matched_patterns)
+                return log_oom();
+
+        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(v, "Interfaces")) {
+                char ifindex_str[DECIMAL_STR_MAX(int64_t)];
+                const char *name;
+                int64_t index;
+                size_t pos;
+
+                name = sd_json_variant_string(sd_json_variant_by_key(i, "Name"));
+                index = sd_json_variant_integer(sd_json_variant_by_key(i, "Index"));
+                xsprintf(ifindex_str, "%" PRIi64, index);
+
+                if (!strv_fnmatch_full(patterns, ifindex_str, 0, &pos) &&
+                    !strv_fnmatch_full(patterns, name, 0, &pos)) {
+                        bool match = false;
+                        sd_json_variant *a;
+
+                        JSON_VARIANT_ARRAY_FOREACH(a, sd_json_variant_by_key(i, "AlternativeNames"))
+                                if (strv_fnmatch_full(patterns, sd_json_variant_string(a), 0, &pos)) {
+                                        match = true;
+                                        break;
+                                }
+
+                        if (!match)
+                                continue;
+                }
+
+                matched_patterns[pos] = true;
+                sd_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;
+}
+
+int dump_description(int argc, char *argv[]) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r;
+
+        if (arg_json_format_flags == SD_JSON_FORMAT_OFF)
+                return 0;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        if (arg_all || argc <= 1)
+                r = dump_manager_description(bus);
+        else
+                r = dump_link_description(bus, strv_skip(argv, 1));
+        if (r < 0)
+                return r;
+
+        return 1; /* done */
+}
diff --git a/src/network/networkctl-description.h b/src/network/networkctl-description.h
new file mode 100644 (file)
index 0000000..6f12489
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int dump_description(int argc, char *argv[]);
diff --git a/src/network/networkctl-dump-util.c b/src/network/networkctl-dump-util.c
new file mode 100644 (file)
index 0000000..d8022eb
--- /dev/null
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "format-util.h"
+#include "in-addr-util.h"
+#include "local-addresses.h"
+#include "networkctl-dump-util.h"
+#include "stdio-util.h"
+#include "strv.h"
+
+int dump_list(Table *table, const char *key, char * const *l) {
+        int r;
+
+        assert(table);
+        assert(key);
+
+        if (strv_isempty(l))
+                return 0;
+
+        r = table_add_many(table,
+                           TABLE_FIELD, key,
+                           TABLE_STRV, l);
+        if (r < 0)
+                return table_log_add_error(r);
+
+        return 0;
+}
+
+/* IEEE Organizationally Unique Identifier vendor string */
+int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret) {
+        _cleanup_free_ char *desc = NULL;
+        const char *description;
+        char modalias[STRLEN("OUI:XXYYXXYYXXYY") + 1];
+        int r;
+
+        assert(ret);
+
+        if (!hwdb || !mac)
+                return -EINVAL;
+
+        /* skip commonly misused 00:00:00 (Xerox) prefix */
+        if (memcmp(mac, "\0\0\0", 3) == 0)
+                return -EINVAL;
+
+        xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR, ETHER_ADDR_FORMAT_VAL(*mac));
+
+        r = sd_hwdb_get(hwdb, modalias, "ID_OUI_FROM_DATABASE", &description);
+        if (r < 0)
+                return r;
+
+        desc = strdup(description);
+        if (!desc)
+                return -ENOMEM;
+
+        *ret = TAKE_PTR(desc);
+
+        return 0;
+}
+
+static int get_gateway_description(
+                sd_netlink *rtnl,
+                sd_hwdb *hwdb,
+                int ifindex,
+                int family,
+                union in_addr_union *gateway,
+                char **ret) {
+
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+        int r;
+
+        assert(rtnl);
+        assert(ifindex >= 0);
+        assert(IN_SET(family, AF_INET, AF_INET6));
+        assert(gateway);
+        assert(ret);
+
+        r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_GETNEIGH, ifindex, family);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_set_request_dump(req, true);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_call(rtnl, req, 0, &reply);
+        if (r < 0)
+                return r;
+
+        for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
+                union in_addr_union gw = IN_ADDR_NULL;
+                struct ether_addr mac = ETHER_ADDR_NULL;
+                uint16_t type;
+                int ifi, fam;
+
+                r = sd_netlink_message_get_errno(m);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to get netlink message, ignoring: %m");
+                        continue;
+                }
+
+                r = sd_netlink_message_get_type(m, &type);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to get netlink message type, ignoring: %m");
+                        continue;
+                }
+
+                if (type != RTM_NEWNEIGH) {
+                        log_error("Got unexpected netlink message type %u, ignoring.", type);
+                        continue;
+                }
+
+                r = sd_rtnl_message_neigh_get_family(m, &fam);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to get rtnl family, ignoring: %m");
+                        continue;
+                }
+
+                if (fam != family) {
+                        log_error("Got invalid rtnl family %d, ignoring.", fam);
+                        continue;
+                }
+
+                r = sd_rtnl_message_neigh_get_ifindex(m, &ifi);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to get rtnl ifindex, ignoring: %m");
+                        continue;
+                }
+
+                if (ifindex > 0 && ifi != ifindex)
+                        continue;
+
+                switch (fam) {
+
+                case AF_INET:
+                        r = sd_netlink_message_read_in_addr(m, NDA_DST, &gw.in);
+                        if (r < 0)
+                                continue;
+
+                        break;
+
+                case AF_INET6:
+                        r = sd_netlink_message_read_in6_addr(m, NDA_DST, &gw.in6);
+                        if (r < 0)
+                                continue;
+
+                        break;
+
+                default:
+                        assert_not_reached();
+                }
+
+                if (!in_addr_equal(fam, &gw, gateway))
+                        continue;
+
+                r = sd_netlink_message_read(m, NDA_LLADDR, sizeof(mac), &mac);
+                if (r < 0)
+                        continue;
+
+                r = ieee_oui(hwdb, &mac, ret);
+                if (r < 0)
+                        continue;
+
+                return 0;
+        }
+
+        return -ENODATA;
+}
+
+int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex) {
+        _cleanup_free_ struct local_address *local_addrs = NULL;
+        _cleanup_strv_free_ char **buf = NULL;
+        int r, n;
+
+        assert(rtnl);
+        assert(table);
+
+        n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local_addrs);
+        if (n <= 0)
+                return n;
+
+        FOREACH_ARRAY(local, local_addrs, n) {
+                _cleanup_free_ char *description = NULL;
+
+                r = get_gateway_description(rtnl, hwdb, local->ifindex, local->family, &local->address, &description);
+                if (r < 0)
+                        log_debug_errno(r, "Could not get description of gateway, ignoring: %m");
+
+                /* Show interface name for the entry if we show entries for all interfaces */
+                r = strv_extendf(&buf, "%s%s%s%s%s%s",
+                                 IN_ADDR_TO_STRING(local->family, &local->address),
+                                 description ? " (" : "",
+                                 strempty(description),
+                                 description ? ")" : "",
+                                 ifindex <= 0 ? " on " : "",
+                                 ifindex <= 0 ? FORMAT_IFNAME_FULL(local->ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
+                if (r < 0)
+                        return log_oom();
+        }
+
+        return dump_list(table, "Gateway", buf);
+}
+
+int dump_addresses(
+                sd_netlink *rtnl,
+                sd_dhcp_lease *lease,
+                Table *table,
+                int ifindex) {
+
+        _cleanup_free_ struct local_address *local_addrs = NULL;
+        _cleanup_strv_free_ char **buf = NULL;
+        struct in_addr dhcp4_address = {};
+        int r, n;
+
+        assert(rtnl);
+        assert(table);
+
+        n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local_addrs);
+        if (n <= 0)
+                return n;
+
+        if (lease)
+                (void) sd_dhcp_lease_get_address(lease, &dhcp4_address);
+
+        FOREACH_ARRAY(local, local_addrs, n) {
+                struct in_addr server_address;
+                bool dhcp4 = false;
+
+                if (local->family == AF_INET && in4_addr_equal(&local->address.in, &dhcp4_address))
+                        dhcp4 = sd_dhcp_lease_get_server_identifier(lease, &server_address) >= 0;
+
+                r = strv_extendf(&buf, "%s%s%s%s%s%s",
+                                 IN_ADDR_TO_STRING(local->family, &local->address),
+                                 dhcp4 ? " (DHCPv4 via " : "",
+                                 dhcp4 ? IN4_ADDR_TO_STRING(&server_address) : "",
+                                 dhcp4 ? ")" : "",
+                                 ifindex <= 0 ? " on " : "",
+                                 ifindex <= 0 ? FORMAT_IFNAME_FULL(local->ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
+                if (r < 0)
+                        return log_oom();
+        }
+
+        return dump_list(table, "Address", buf);
+}
diff --git a/src/network/networkctl-dump-util.h b/src/network/networkctl-dump-util.h
new file mode 100644 (file)
index 0000000..c4dfae9
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-dhcp-lease.h"
+#include "sd-hwdb.h"
+#include "sd-netlink.h"
+
+#include "ether-addr-util.h"
+#include "format-table.h"
+
+int dump_list(Table *table, const char *key, char * const *l);
+int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret);
+int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex);
+int dump_addresses(sd_netlink *rtnl, sd_dhcp_lease *lease, Table *table, int ifindex);
diff --git a/src/network/networkctl-journal.c b/src/network/networkctl-journal.c
new file mode 100644 (file)
index 0000000..2dc536c
--- /dev/null
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "journal-internal.h"
+#include "logs-show.h"
+#include "networkctl.h"
+#include "networkctl-journal.h"
+#include "terminal-util.h"
+
+static OutputFlags get_output_flags(void) {
+        return
+                arg_all * OUTPUT_SHOW_ALL |
+                (arg_full || !on_tty() || pager_have()) * OUTPUT_FULL_WIDTH |
+                colors_enabled() * OUTPUT_COLOR;
+}
+
+int show_logs(int ifindex, const char *ifname) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        int r;
+
+        assert(ifindex == 0 || ifname);
+
+        if (arg_lines == 0)
+                return 0;
+
+        r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open journal: %m");
+
+        r = add_match_this_boot(j, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add boot matches: %m");
+
+        if (ifindex > 0) {
+                (void) (
+                       (r = journal_add_matchf(j, "_KERNEL_DEVICE=n%i", ifindex)) || /* kernel */
+                       (r = sd_journal_add_disjunction(j)) ||
+                       (r = journal_add_match_pair(j, "INTERFACE", ifname)) || /* networkd */
+                       (r = sd_journal_add_disjunction(j)) ||
+                       (r = journal_add_match_pair(j, "DEVICE", ifname)) /* udevd */
+                );
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add link matches: %m");
+        } else {
+                r = add_matches_for_unit(j, "systemd-networkd.service");
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add unit matches: %m");
+
+                r = add_matches_for_unit(j, "systemd-networkd-wait-online.service");
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add unit matches: %m");
+        }
+
+        return show_journal(
+                        stdout,
+                        j,
+                        OUTPUT_SHORT,
+                        0,
+                        0,
+                        arg_lines,
+                        get_output_flags() | OUTPUT_BEGIN_NEWLINE,
+                        NULL);
+}
diff --git a/src/network/networkctl-journal.h b/src/network/networkctl-journal.h
new file mode 100644 (file)
index 0000000..da291e0
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int show_logs(int ifindex, const char *ifname);
diff --git a/src/network/networkctl-link-info.c b/src/network/networkctl-link-info.c
new file mode 100644 (file)
index 0000000..f356d3c
--- /dev/null
@@ -0,0 +1,428 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/if_tunnel.h>
+
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-util.h"
+#include "device-util.h"
+#include "fd-util.h"
+#include "glob-util.h"
+#include "netlink-util.h"
+#include "networkctl-link-info.h"
+#include "networkctl-util.h"
+#include "sort-util.h"
+#include "strv.h"
+#include "strxcpyx.h"
+#include "wifi-util.h"
+
+/* use 128 kB for receive socket kernel queue, we shouldn't need more here */
+#define RCVBUF_SIZE    (128*1024)
+
+LinkInfo* link_info_array_free(LinkInfo *array) {
+        for (unsigned i = 0; array && array[i].needs_freeing; i++) {
+                sd_device_unref(array[i].sd_device);
+                free(array[i].netdev_kind);
+                free(array[i].ssid);
+                free(array[i].qdisc);
+                strv_free(array[i].alternative_names);
+        }
+
+        return mfree(array);
+}
+
+static int link_info_compare(const LinkInfo *a, const LinkInfo *b) {
+        return CMP(a->ifindex, b->ifindex);
+}
+
+static int decode_netdev(sd_netlink_message *m, LinkInfo *info) {
+        int r;
+
+        assert(m);
+        assert(info);
+
+        r = sd_netlink_message_enter_container(m, IFLA_LINKINFO);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_read_string_strdup(m, IFLA_INFO_KIND, &info->netdev_kind);
+        if (r < 0) {
+                (void) sd_netlink_message_exit_container(m);
+                return r;
+        }
+
+        r = sd_netlink_message_enter_container(m, IFLA_INFO_DATA);
+        if (r < 0)
+                return r;
+
+        if (streq(info->netdev_kind, "bridge")) {
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_FORWARD_DELAY, &info->forward_delay);
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_HELLO_TIME, &info->hello_time);
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_MAX_AGE, &info->max_age);
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_AGEING_TIME, &info->ageing_time);
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_STP_STATE, &info->stp_state);
+                (void) sd_netlink_message_read_u32(m, IFLA_BRPORT_COST, &info->cost);
+                (void) sd_netlink_message_read_u16(m, IFLA_BR_PRIORITY, &info->priority);
+                (void) sd_netlink_message_read_u8(m, IFLA_BR_MCAST_IGMP_VERSION, &info->mcast_igmp_version);
+                (void) sd_netlink_message_read_u8(m, IFLA_BRPORT_STATE, &info->port_state);
+                if (sd_netlink_message_read_u32(m, IFLA_BR_FDB_MAX_LEARNED, &info->fdb_max_learned) >= 0 &&
+                    sd_netlink_message_read_u32(m, IFLA_BR_FDB_N_LEARNED, &info->fdb_n_learned) >= 0)
+                        info->has_fdb_learned = true;
+        } if (streq(info->netdev_kind, "bond")) {
+                (void) sd_netlink_message_read_u8(m, IFLA_BOND_MODE, &info->mode);
+                (void) sd_netlink_message_read_u32(m, IFLA_BOND_MIIMON, &info->miimon);
+                (void) sd_netlink_message_read_u32(m, IFLA_BOND_DOWNDELAY, &info->downdelay);
+                (void) sd_netlink_message_read_u32(m, IFLA_BOND_UPDELAY, &info->updelay);
+        } else if (streq(info->netdev_kind, "vxlan")) {
+                (void) sd_netlink_message_read_u32(m, IFLA_VXLAN_ID, &info->vxlan_info.vni);
+
+                r = sd_netlink_message_read_in_addr(m, IFLA_VXLAN_GROUP, &info->vxlan_info.group.in);
+                if (r >= 0)
+                        info->vxlan_info.group_family = AF_INET;
+                else {
+                        r = sd_netlink_message_read_in6_addr(m, IFLA_VXLAN_GROUP6, &info->vxlan_info.group.in6);
+                        if (r >= 0)
+                                info->vxlan_info.group_family = AF_INET6;
+                }
+
+                r = sd_netlink_message_read_in_addr(m, IFLA_VXLAN_LOCAL, &info->vxlan_info.local.in);
+                if (r >= 0)
+                        info->vxlan_info.local_family = AF_INET;
+                else {
+                        r = sd_netlink_message_read_in6_addr(m, IFLA_VXLAN_LOCAL6, &info->vxlan_info.local.in6);
+                        if (r >= 0)
+                                info->vxlan_info.local_family = AF_INET6;
+                }
+
+                (void) sd_netlink_message_read_u32(m, IFLA_VXLAN_LINK, &info->vxlan_info.link);
+                (void) sd_netlink_message_read_u16(m, IFLA_VXLAN_PORT, &info->vxlan_info.dest_port);
+                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_PROXY, &info->vxlan_info.proxy);
+                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_LEARNING, &info->vxlan_info.learning);
+                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_RSC, &info->vxlan_info.rsc);
+                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_L3MISS, &info->vxlan_info.l3miss);
+                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_L2MISS, &info->vxlan_info.l2miss);
+                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_TOS, &info->vxlan_info.tos);
+                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_TTL, &info->vxlan_info.ttl);
+        } else if (streq(info->netdev_kind, "vlan"))
+                (void) sd_netlink_message_read_u16(m, IFLA_VLAN_ID, &info->vlan_id);
+        else if (STR_IN_SET(info->netdev_kind, "ipip", "sit")) {
+                (void) sd_netlink_message_read_in_addr(m, IFLA_IPTUN_LOCAL, &info->local.in);
+                (void) sd_netlink_message_read_in_addr(m, IFLA_IPTUN_REMOTE, &info->remote.in);
+        } else if (streq(info->netdev_kind, "geneve")) {
+                (void) sd_netlink_message_read_u32(m, IFLA_GENEVE_ID, &info->vni);
+
+                r = sd_netlink_message_read_in_addr(m, IFLA_GENEVE_REMOTE, &info->remote.in);
+                if (r >= 0)
+                        info->has_tunnel_ipv4 = true;
+                else
+                        (void) sd_netlink_message_read_in6_addr(m, IFLA_GENEVE_REMOTE6, &info->remote.in6);
+
+                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_TTL, &info->ttl);
+                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_TTL_INHERIT, &info->inherit);
+                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_TOS, &info->tos);
+                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_DF, &info->df);
+                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_UDP_CSUM, &info->csum);
+                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_TX, &info->csum6_tx);
+                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, &info->csum6_rx);
+                (void) sd_netlink_message_read_u16(m, IFLA_GENEVE_PORT, &info->tunnel_port);
+                (void) sd_netlink_message_read_u32(m, IFLA_GENEVE_LABEL, &info->label);
+        } else if (STR_IN_SET(info->netdev_kind, "gre", "gretap", "erspan")) {
+                (void) sd_netlink_message_read_in_addr(m, IFLA_GRE_LOCAL, &info->local.in);
+                (void) sd_netlink_message_read_in_addr(m, IFLA_GRE_REMOTE, &info->remote.in);
+        } else if (STR_IN_SET(info->netdev_kind, "ip6gre", "ip6gretap", "ip6erspan")) {
+                (void) sd_netlink_message_read_in6_addr(m, IFLA_GRE_LOCAL, &info->local.in6);
+                (void) sd_netlink_message_read_in6_addr(m, IFLA_GRE_REMOTE, &info->remote.in6);
+        } else if (streq(info->netdev_kind, "vti")) {
+                (void) sd_netlink_message_read_in_addr(m, IFLA_VTI_LOCAL, &info->local.in);
+                (void) sd_netlink_message_read_in_addr(m, IFLA_VTI_REMOTE, &info->remote.in);
+        } else if (streq(info->netdev_kind, "vti6")) {
+                (void) sd_netlink_message_read_in6_addr(m, IFLA_VTI_LOCAL, &info->local.in6);
+                (void) sd_netlink_message_read_in6_addr(m, IFLA_VTI_REMOTE, &info->remote.in6);
+        } else if (STR_IN_SET(info->netdev_kind, "macvlan", "macvtap"))
+                (void) sd_netlink_message_read_u32(m, IFLA_MACVLAN_MODE, &info->macvlan_mode);
+        else if (streq(info->netdev_kind, "ipvlan")) {
+                (void) sd_netlink_message_read_u16(m, IFLA_IPVLAN_MODE, &info->ipvlan_mode);
+                (void) sd_netlink_message_read_u16(m, IFLA_IPVLAN_FLAGS, &info->ipvlan_flags);
+        }
+
+        (void) sd_netlink_message_exit_container(m);
+        (void) sd_netlink_message_exit_container(m);
+
+        return 0;
+}
+
+static int decode_link(
+                sd_netlink_message *m,
+                LinkInfo *info,
+                char * const *patterns,
+                bool matched_patterns[]) {
+
+        _cleanup_strv_free_ char **altnames = NULL;
+        const char *name, *qdisc;
+        int ifindex, r;
+        uint16_t type;
+
+        assert(m);
+        assert(info);
+
+        r = sd_netlink_message_get_type(m, &type);
+        if (r < 0)
+                return r;
+
+        if (type != RTM_NEWLINK)
+                return 0;
+
+        r = sd_rtnl_message_link_get_ifindex(m, &ifindex);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_read_string(m, IFLA_IFNAME, &name);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_read_strv(m, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &altnames);
+        if (r < 0 && r != -ENODATA)
+                return r;
+
+        if (patterns) {
+                char str[DECIMAL_STR_MAX(int)];
+                size_t pos;
+
+                assert(matched_patterns);
+
+                xsprintf(str, "%i", ifindex);
+                if (!strv_fnmatch_full(patterns, str, 0, &pos) &&
+                    !strv_fnmatch_full(patterns, name, 0, &pos)) {
+                        bool match = false;
+
+                        STRV_FOREACH(p, altnames)
+                                if (strv_fnmatch_full(patterns, *p, 0, &pos)) {
+                                        match = true;
+                                        break;
+                                }
+                        if (!match)
+                                return 0;
+                }
+
+                matched_patterns[pos] = true;
+        }
+
+        r = sd_rtnl_message_link_get_type(m, &info->iftype);
+        if (r < 0)
+                return r;
+
+        strscpy(info->name, sizeof info->name, name);
+        info->ifindex = ifindex;
+        info->alternative_names = TAKE_PTR(altnames);
+
+        info->has_hw_address =
+                netlink_message_read_hw_addr(m, IFLA_ADDRESS, &info->hw_address) >= 0 &&
+                info->hw_address.length > 0;
+
+        info->has_permanent_hw_address =
+                (netlink_message_read_hw_addr(m, IFLA_PERM_ADDRESS, &info->permanent_hw_address) >= 0 ||
+                 ethtool_get_permanent_hw_addr(NULL, info->name, &info->permanent_hw_address) >= 0) &&
+                !hw_addr_is_null(&info->permanent_hw_address) &&
+                !hw_addr_equal(&info->permanent_hw_address, &info->hw_address);
+
+        (void) sd_netlink_message_read_u32(m, IFLA_MTU, &info->mtu);
+        (void) sd_netlink_message_read_u32(m, IFLA_MIN_MTU, &info->min_mtu);
+        (void) sd_netlink_message_read_u32(m, IFLA_MAX_MTU, &info->max_mtu);
+
+        info->has_rx_queues =
+                sd_netlink_message_read_u32(m, IFLA_NUM_RX_QUEUES, &info->rx_queues) >= 0 &&
+                info->rx_queues > 0;
+
+        info->has_tx_queues =
+                sd_netlink_message_read_u32(m, IFLA_NUM_TX_QUEUES, &info->tx_queues) >= 0 &&
+                info->tx_queues > 0;
+
+        if (sd_netlink_message_read(m, IFLA_STATS64, sizeof info->stats64, &info->stats64) >= 0)
+                info->has_stats64 = true;
+        else if (sd_netlink_message_read(m, IFLA_STATS, sizeof info->stats, &info->stats) >= 0)
+                info->has_stats = true;
+
+        r = sd_netlink_message_read_string(m, IFLA_QDISC, &qdisc);
+        if (r >= 0) {
+                info->qdisc = strdup(qdisc);
+                if (!info->qdisc)
+                        return log_oom();
+        }
+
+        (void) sd_netlink_message_read_u32(m, IFLA_MASTER, &info->master);
+
+        r = sd_netlink_message_enter_container(m, IFLA_AF_SPEC);
+        if (r >= 0) {
+                r = sd_netlink_message_enter_container(m, AF_INET6);
+                if (r >= 0) {
+                        r = sd_netlink_message_read_u8(m, IFLA_INET6_ADDR_GEN_MODE, &info->addr_gen_mode);
+                        if (r >= 0 && IN_SET(info->addr_gen_mode,
+                                             IN6_ADDR_GEN_MODE_EUI64,
+                                             IN6_ADDR_GEN_MODE_NONE,
+                                             IN6_ADDR_GEN_MODE_STABLE_PRIVACY,
+                                             IN6_ADDR_GEN_MODE_RANDOM))
+                                info->has_ipv6_address_generation_mode = true;
+
+                        (void) sd_netlink_message_exit_container(m);
+                }
+                (void) sd_netlink_message_exit_container(m);
+        }
+
+        /* fill kind info */
+        (void) decode_netdev(m, info);
+
+        return 1;
+}
+
+static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(bus);
+        assert(link);
+
+        r = link_get_property(bus, link->ifindex, &error, &reply, "org.freedesktop.network1.Link", "BitRates", "(tt)");
+        if (r < 0) {
+                bool quiet = sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY,
+                                                            BUS_ERROR_SPEED_METER_INACTIVE);
+
+                return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING,
+                                      r, "Failed to query link bit rates: %s", bus_error_message(&error, r));
+        }
+
+        r = sd_bus_message_read(reply, "(tt)", &link->tx_bitrate, &link->rx_bitrate);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        link->has_bitrates = link->tx_bitrate != UINT64_MAX && link->rx_bitrate != UINT64_MAX;
+
+        return 0;
+}
+
+static void acquire_ether_link_info(int *fd, LinkInfo *link) {
+        assert(fd);
+        assert(link);
+
+        if (ethtool_get_link_info(fd,
+                                  link->name,
+                                  &link->autonegotiation,
+                                  &link->speed,
+                                  &link->duplex,
+                                  &link->port) >= 0)
+                link->has_ethtool_link_info = true;
+}
+
+static void acquire_wlan_link_info(LinkInfo *link) {
+        _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL;
+        int r, k = 0;
+
+        assert(link);
+
+        if (!link->sd_device)
+                return;
+
+        if (!device_is_devtype(link->sd_device, "wlan"))
+                return;
+
+        r = sd_genl_socket_open(&genl);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to open generic netlink socket: %m");
+                return;
+        }
+
+        (void) sd_netlink_increase_rxbuf(genl, RCVBUF_SIZE);
+
+        r = wifi_get_interface(genl, link->ifindex, &link->wlan_iftype, &link->ssid);
+        if (r < 0)
+                log_debug_errno(r, "%s: failed to query ssid: %m", link->name);
+
+        if (link->wlan_iftype == NL80211_IFTYPE_STATION) {
+                k = wifi_get_station(genl, link->ifindex, &link->bssid);
+                if (k < 0)
+                        log_debug_errno(k, "%s: failed to query bssid: %m", link->name);
+        }
+
+        link->has_wlan_link_info = r > 0 || k > 0;
+}
+
+int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+        _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
+        _cleanup_free_ bool *matched_patterns = NULL;
+        _cleanup_close_ int fd = -EBADF;
+        size_t c = 0;
+        int r;
+
+        assert(rtnl);
+        assert(ret);
+
+        r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
+        if (r < 0)
+                return rtnl_log_create_error(r);
+
+        r = sd_netlink_message_set_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");
+
+        if (patterns) {
+                matched_patterns = new0(bool, strv_length(patterns));
+                if (!matched_patterns)
+                        return log_oom();
+        }
+
+        for (sd_netlink_message *i = reply; i; i = sd_netlink_message_next(i)) {
+                if (!GREEDY_REALLOC0(links, c + 2)) /* We keep one trailing one as marker */
+                        return -ENOMEM;
+
+                r = decode_link(i, links + c, patterns, matched_patterns);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        continue;
+
+                links[c].needs_freeing = true;
+
+                (void) sd_device_new_from_ifindex(&links[c].sd_device, links[c].ifindex);
+
+                acquire_ether_link_info(&fd, &links[c]);
+                acquire_wlan_link_info(&links[c]);
+
+                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]);
+        }
+
+        typesafe_qsort(links, c, link_info_compare);
+
+        if (bus)
+                FOREACH_ARRAY(link, links, c)
+                        (void) acquire_link_bitrates(bus, link);
+
+        *ret = TAKE_PTR(links);
+
+        if (patterns && c == 0)
+                log_warning("No interfaces matched.");
+
+        return (int) c;
+}
diff --git a/src/network/networkctl-link-info.h b/src/network/networkctl-link-info.h
new file mode 100644 (file)
index 0000000..5dc5e7e
--- /dev/null
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/if.h>
+#include <linux/if_link.h>
+#include <linux/nl80211.h>
+#include <stdint.h>
+
+#include "sd-bus.h"
+#include "sd-device.h"
+#include "sd-netlink.h"
+
+#include "ether-addr-util.h"
+#include "ethtool-util.h"
+#include "in-addr-util.h"
+
+typedef struct VxLanInfo {
+        uint32_t vni;
+        uint32_t link;
+
+        int local_family;
+        int group_family;
+
+        union in_addr_union local;
+        union in_addr_union group;
+
+        uint16_t dest_port;
+
+        uint8_t proxy;
+        uint8_t learning;
+        uint8_t rsc;
+        uint8_t l2miss;
+        uint8_t l3miss;
+        uint8_t tos;
+        uint8_t ttl;
+} VxLanInfo;
+
+typedef struct LinkInfo {
+        char name[IFNAMSIZ+1];
+        char *netdev_kind;
+        sd_device *sd_device;
+        int ifindex;
+        unsigned short iftype;
+        struct hw_addr_data hw_address;
+        struct hw_addr_data permanent_hw_address;
+        uint32_t master;
+        uint32_t mtu;
+        uint32_t min_mtu;
+        uint32_t max_mtu;
+        uint32_t tx_queues;
+        uint32_t rx_queues;
+        uint8_t addr_gen_mode;
+        char *qdisc;
+        char **alternative_names;
+
+        union {
+                struct rtnl_link_stats64 stats64;
+                struct rtnl_link_stats stats;
+        };
+
+        uint64_t tx_bitrate;
+        uint64_t rx_bitrate;
+
+        /* bridge info */
+        uint32_t forward_delay;
+        uint32_t hello_time;
+        uint32_t max_age;
+        uint32_t ageing_time;
+        uint32_t stp_state;
+        uint32_t cost;
+        uint16_t priority;
+        uint8_t mcast_igmp_version;
+        uint8_t port_state;
+        uint32_t fdb_max_learned;
+        uint32_t fdb_n_learned;
+        bool has_fdb_learned;
+
+        /* vxlan info */
+        VxLanInfo vxlan_info;
+
+        /* vlan info */
+        uint16_t vlan_id;
+
+        /* tunnel info */
+        uint8_t ttl;
+        uint8_t tos;
+        uint8_t inherit;
+        uint8_t df;
+        uint8_t csum;
+        uint8_t csum6_tx;
+        uint8_t csum6_rx;
+        uint16_t tunnel_port;
+        uint32_t vni;
+        uint32_t label;
+        union in_addr_union local;
+        union in_addr_union remote;
+
+        /* bonding info */
+        uint8_t mode;
+        uint32_t miimon;
+        uint32_t updelay;
+        uint32_t downdelay;
+
+        /* macvlan and macvtap info */
+        uint32_t macvlan_mode;
+
+        /* ipvlan info */
+        uint16_t ipvlan_mode;
+        uint16_t ipvlan_flags;
+
+        /* ethtool info */
+        int autonegotiation;
+        uint64_t speed;
+        Duplex duplex;
+        NetDevPort port;
+
+        /* wlan info */
+        enum nl80211_iftype wlan_iftype;
+        char *ssid;
+        struct ether_addr bssid;
+
+        bool has_hw_address:1;
+        bool has_permanent_hw_address:1;
+        bool has_tx_queues:1;
+        bool has_rx_queues:1;
+        bool has_stats64:1;
+        bool has_stats:1;
+        bool has_bitrates:1;
+        bool has_ethtool_link_info:1;
+        bool has_wlan_link_info:1;
+        bool has_tunnel_ipv4:1;
+        bool has_ipv6_address_generation_mode:1;
+
+        bool needs_freeing:1;
+} LinkInfo;
+
+LinkInfo* link_info_array_free(LinkInfo *array);
+DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_array_free);
+
+int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret);
diff --git a/src/network/networkctl-list.c b/src/network/networkctl-list.c
new file mode 100644 (file)
index 0000000..a23930d
--- /dev/null
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-network.h"
+
+#include "format-table.h"
+#include "netif-util.h"
+#include "networkctl.h"
+#include "networkctl-description.h"
+#include "networkctl-link-info.h"
+#include "networkctl-list.h"
+#include "networkctl-util.h"
+
+int list_links(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        TableCell *cell;
+        int c, r;
+
+        r = dump_description(argc, argv);
+        if (r != 0)
+                return r;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_open(&rtnl);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to netlink: %m");
+
+        c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links);
+        if (c < 0)
+                return c;
+
+        pager_open(arg_pager_flags);
+
+        table = table_new("idx", "link", "type", "operational", "setup");
+        if (!table)
+                return log_oom();
+
+        if (arg_full)
+                table_set_width(table, 0);
+
+        table_set_header(table, arg_legend);
+        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+        assert_se(cell = table_get_cell(table, 0, 0));
+        (void) table_set_minimum_width(table, cell, 3);
+        (void) table_set_weight(table, cell, 0);
+        (void) table_set_ellipsize_percent(table, cell, 100);
+        (void) table_set_align_percent(table, cell, 100);
+
+        assert_se(cell = table_get_cell(table, 0, 1));
+        (void) table_set_ellipsize_percent(table, cell, 100);
+
+        FOREACH_ARRAY(link, links, c) {
+                _cleanup_free_ char *setup_state = NULL, *operational_state = NULL;
+                _cleanup_free_ char *t = NULL;
+                const char *on_color_operational, *on_color_setup;
+
+                (void) sd_network_link_get_operational_state(link->ifindex, &operational_state);
+                operational_state_to_color(link->name, operational_state, &on_color_operational, NULL);
+
+                (void) sd_network_link_get_setup_state(link->ifindex, &setup_state);
+                setup_state_to_color(setup_state, &on_color_setup, NULL);
+
+                r = net_get_type_string(link->sd_device, link->iftype, &t);
+                if (r == -ENOMEM)
+                        return log_oom();
+
+                r = table_add_many(table,
+                                   TABLE_INT, link->ifindex,
+                                   TABLE_STRING, link->name,
+                                   TABLE_STRING, t,
+                                   TABLE_STRING, operational_state,
+                                   TABLE_SET_COLOR, on_color_operational,
+                                   TABLE_STRING, setup_state ?: "unmanaged",
+                                   TABLE_SET_COLOR, on_color_setup);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        r = table_print(table, NULL);
+        if (r < 0)
+                return table_log_print_error(r);
+
+        if (arg_legend)
+                printf("\n%i links listed.\n", c);
+
+        return 0;
+}
diff --git a/src/network/networkctl-list.h b/src/network/networkctl-list.h
new file mode 100644 (file)
index 0000000..0ee8dba
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int list_links(int argc, char *argv[], void *userdata);
diff --git a/src/network/networkctl-lldp.c b/src/network/networkctl-lldp.c
new file mode 100644 (file)
index 0000000..7836cb5
--- /dev/null
@@ -0,0 +1,311 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "json-util.h"
+#include "networkctl.h"
+#include "networkctl-dump-util.h"
+#include "networkctl-lldp.h"
+#include "networkctl-util.h"
+#include "stdio-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "varlink-util.h"
+
+typedef struct InterfaceInfo {
+        int ifindex;
+        const char *ifname;
+        char **altnames;
+        sd_json_variant *v;
+} InterfaceInfo;
+
+static void interface_info_done(InterfaceInfo *p) {
+        if (!p)
+                return;
+
+        strv_free(p->altnames);
+        sd_json_variant_unref(p->v);
+}
+
+static const sd_json_dispatch_field interface_info_dispatch_table[] = {
+        { "InterfaceIndex",            _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int,          offsetof(InterfaceInfo, ifindex),  SD_JSON_MANDATORY },
+        { "InterfaceName",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(InterfaceInfo, ifname),   SD_JSON_MANDATORY },
+        { "InterfaceAlternativeNames", SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,         offsetof(InterfaceInfo, altnames), 0                 },
+        { "Neighbors",                 SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_variant,      offsetof(InterfaceInfo, v),        0                 },
+        {},
+};
+
+typedef struct LLDPNeighborInfo {
+        const char *chassis_id;
+        const char *port_id;
+        const char *port_description;
+        const char *system_name;
+        const char *system_description;
+        uint16_t capabilities;
+} LLDPNeighborInfo;
+
+static const sd_json_dispatch_field lldp_neighbor_dispatch_table[] = {
+        { "ChassisID",           SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, chassis_id),         0 },
+        { "PortID",              SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_id),            0 },
+        { "PortDescription",     SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_description),   0 },
+        { "SystemName",          SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_name),        0 },
+        { "SystemDescription",   SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_description), 0 },
+        { "EnabledCapabilities", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16,       offsetof(LLDPNeighborInfo, capabilities),       0 },
+        {},
+};
+
+int dump_lldp_neighbors(sd_varlink *vl, Table *table, int ifindex) {
+        _cleanup_strv_free_ char **buf = NULL;
+        sd_json_variant *reply;
+        int r;
+
+        assert(vl);
+        assert(table);
+        assert(ifindex > 0);
+
+        r = varlink_callbo_and_log(
+                        vl,
+                        "io.systemd.Network.GetLLDPNeighbors",
+                        &reply,
+                        SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", ifindex));
+        if (r < 0)
+                return r;
+
+        sd_json_variant *i;
+        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(reply, "Neighbors")) {
+                _cleanup_(interface_info_done) InterfaceInfo info = {};
+
+                r = sd_json_dispatch(i, interface_info_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &info);
+                if (r < 0)
+                        return r;
+
+                if (info.ifindex != ifindex)
+                        continue;
+
+                sd_json_variant *neighbor;
+                JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) {
+                        LLDPNeighborInfo neighbor_info = {};
+
+                        r = sd_json_dispatch(neighbor, lldp_neighbor_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &neighbor_info);
+                        if (r < 0)
+                                return r;
+
+                        r = strv_extendf(&buf, "%s%s%s%s on port %s%s%s%s",
+                                         strna(neighbor_info.system_name),
+                                         isempty(neighbor_info.system_description) ? "" : " (",
+                                         strempty(neighbor_info.system_description),
+                                         isempty(neighbor_info.system_description) ? "" : ")",
+                                         strna(neighbor_info.port_id),
+                                         isempty(neighbor_info.port_description) ? "" : " (",
+                                         strempty(neighbor_info.port_description),
+                                         isempty(neighbor_info.port_description) ? "" : ")");
+                        if (r < 0)
+                                return log_oom();
+                }
+        }
+
+        return dump_list(table, "Connected To", buf);
+}
+
+static char* lldp_capabilities_to_string(uint64_t x) {
+        static const char characters[] = {
+                'o', 'p', 'b', 'w', 'r', 't', 'd', 'a', 'c', 's', 'm',
+        };
+        char *ret;
+        unsigned i;
+
+        ret = new(char, ELEMENTSOF(characters) + 1);
+        if (!ret)
+                return NULL;
+
+        for (i = 0; i < ELEMENTSOF(characters); i++)
+                ret[i] = (x & (1U << i)) ? characters[i] : '.';
+
+        ret[i] = 0;
+        return ret;
+}
+
+static void lldp_capabilities_legend(uint16_t x) {
+        unsigned cols = columns();
+        static const char* const table[] = {
+                "o - Other",
+                "p - Repeater",
+                "b - Bridge",
+                "w - WLAN Access Point",
+                "r - Router",
+                "t - Telephone",
+                "d - DOCSIS cable device",
+                "a - Station",
+                "c - Customer VLAN",
+                "s - Service VLAN",
+                "m - Two-port MAC Relay (TPMR)",
+        };
+
+        if (x == 0)
+                return;
+
+        printf("\nCapability Flags:\n");
+        for (unsigned w = 0, i = 0; i < ELEMENTSOF(table); i++)
+                if (x & (1U << i) || arg_all) {
+                        bool newline;
+
+                        newline = w + strlen(table[i]) + (w == 0 ? 0 : 2) > cols;
+                        if (newline)
+                                w = 0;
+                        w += printf("%s%s%s", newline ? "\n" : "", w == 0 ? "" : "; ", table[i]);
+                }
+        puts("");
+}
+
+static bool interface_match_pattern(const InterfaceInfo *info, char * const *patterns) {
+        assert(info);
+
+        if (strv_isempty(patterns))
+                return true;
+
+        if (strv_fnmatch(patterns, info->ifname))
+                return true;
+
+        char str[DECIMAL_STR_MAX(int)];
+        xsprintf(str, "%i", info->ifindex);
+        if (strv_fnmatch(patterns, str))
+                return true;
+
+        STRV_FOREACH(a, info->altnames)
+                if (strv_fnmatch(patterns, *a))
+                        return true;
+
+        return false;
+}
+
+static int dump_lldp_neighbors_json(sd_json_variant *reply, char * const *patterns) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *v = NULL;
+        int r;
+
+        assert(reply);
+
+        if (strv_isempty(patterns))
+                return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL);
+
+        /* Filter and dump the result. */
+
+        sd_json_variant *i;
+        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(reply, "Neighbors")) {
+                _cleanup_(interface_info_done) InterfaceInfo info = {};
+
+                r = sd_json_dispatch(i, interface_info_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &info);
+                if (r < 0)
+                        return r;
+
+                if (!interface_match_pattern(&info, patterns))
+                        continue;
+
+                r = sd_json_variant_append_array(&array, i);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to append json variant to array: %m");
+        }
+
+        r = sd_json_buildo(
+                        &v,
+                        SD_JSON_BUILD_PAIR_CONDITION(sd_json_variant_is_blank_array(array), "Neighbors", SD_JSON_BUILD_EMPTY_ARRAY),
+                        SD_JSON_BUILD_PAIR_CONDITION(!sd_json_variant_is_blank_array(array), "Neighbors", SD_JSON_BUILD_VARIANT(array)));
+        if (r < 0)
+                return log_error_errno(r, "Failed to build json varinat: %m");
+
+        return sd_json_variant_dump(v, arg_json_format_flags, NULL, NULL);
+}
+
+int link_lldp_status(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        sd_json_variant *reply;
+        uint64_t all = 0;
+        TableCell *cell;
+        size_t m = 0;
+        int r;
+
+        r = varlink_connect_networkd(&vl);
+        if (r < 0)
+                return r;
+
+        r = varlink_call_and_log(vl, "io.systemd.Network.GetLLDPNeighbors", NULL, &reply);
+        if (r < 0)
+                return r;
+
+        if (arg_json_format_flags != SD_JSON_FORMAT_OFF)
+                return dump_lldp_neighbors_json(reply, strv_skip(argv, 1));
+
+        pager_open(arg_pager_flags);
+
+        table = table_new("index",
+                          "link",
+                          "system-name",
+                          "system-description",
+                          "chassis-id",
+                          "port-id",
+                          "port-description",
+                          "caps");
+        if (!table)
+                return log_oom();
+
+        if (arg_full)
+                table_set_width(table, 0);
+
+        table_set_header(table, arg_legend);
+        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+        table_set_sort(table, (size_t) 0, (size_t) 2);
+        table_hide_column_from_display(table, (size_t) 0);
+
+        /* Make the capabilities not truncated */
+        assert_se(cell = table_get_cell(table, 0, 7));
+        table_set_minimum_width(table, cell, 11);
+
+        sd_json_variant *i;
+        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(reply, "Neighbors")) {
+                _cleanup_(interface_info_done) InterfaceInfo info = {};
+
+                r = sd_json_dispatch(i, interface_info_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &info);
+                if (r < 0)
+                        return r;
+
+                if (!interface_match_pattern(&info, strv_skip(argv, 1)))
+                        continue;
+
+                sd_json_variant *neighbor;
+                JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) {
+                        LLDPNeighborInfo neighbor_info = {};
+
+                        r = sd_json_dispatch(neighbor, lldp_neighbor_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &neighbor_info);
+                        if (r < 0)
+                                return r;
+
+                        all |= neighbor_info.capabilities;
+
+                        _cleanup_free_ char *cap_str = lldp_capabilities_to_string(neighbor_info.capabilities);
+
+                        r = table_add_many(table,
+                                           TABLE_INT,    info.ifindex,
+                                           TABLE_STRING, info.ifname,
+                                           TABLE_STRING, neighbor_info.system_name,
+                                           TABLE_STRING, neighbor_info.system_description,
+                                           TABLE_STRING, neighbor_info.chassis_id,
+                                           TABLE_STRING, neighbor_info.port_id,
+                                           TABLE_STRING, neighbor_info.port_description,
+                                           TABLE_STRING, cap_str);
+                        if (r < 0)
+                                return table_log_add_error(r);
+
+                        m++;
+                }
+        }
+
+        r = table_print(table, NULL);
+        if (r < 0)
+                return table_log_print_error(r);
+
+        if (arg_legend) {
+                lldp_capabilities_legend(all);
+                printf("\n%zu neighbor(s) listed.\n", m);
+        }
+
+        return 0;
+}
diff --git a/src/network/networkctl-lldp.h b/src/network/networkctl-lldp.h
new file mode 100644 (file)
index 0000000..3ec6fe7
--- /dev/null
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-varlink.h"
+
+#include "format-table.h"
+
+int dump_lldp_neighbors(sd_varlink *vl, Table *table, int ifindex);
+int link_lldp_status(int argc, char *argv[], void *userdata);
diff --git a/src/network/networkctl-misc.c b/src/network/networkctl-misc.c
new file mode 100644 (file)
index 0000000..7a14bff
--- /dev/null
@@ -0,0 +1,305 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <net/if.h>
+
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "fd-util.h"
+#include "format-util.h"
+#include "netlink-util.h"
+#include "networkctl.h"
+#include "networkctl-misc.h"
+#include "networkctl-util.h"
+#include "parse-util.h"
+#include "polkit-agent.h"
+#include "set.h"
+#include "strv.h"
+#include "varlink-util.h"
+
+static int link_up_down_send_message(sd_netlink *rtnl, char *command, int index) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+        int r;
+
+        assert(rtnl);
+        assert(index >= 0);
+
+        r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, index);
+        if (r < 0)
+                return rtnl_log_create_error(r);
+
+        if (streq(command, "up"))
+                r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP);
+        else
+                r = sd_rtnl_message_link_set_flags(req, 0, IFF_UP);
+        if (r < 0)
+                return log_error_errno(r, "Could not set link flags: %m");
+
+        r = sd_netlink_call(rtnl, req, 0, NULL);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int link_up_down(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        _cleanup_set_free_ Set *indexes = NULL;
+        int index, r;
+        void *p;
+
+        r = sd_netlink_open(&rtnl);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to netlink: %m");
+
+        indexes = set_new(NULL);
+        if (!indexes)
+                return log_oom();
+
+        for (int i = 1; i < argc; i++) {
+                index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
+                if (index < 0)
+                        return index;
+
+                r = set_put(indexes, INT_TO_PTR(index));
+                if (r < 0)
+                        return log_oom();
+        }
+
+        SET_FOREACH(p, indexes) {
+                index = PTR_TO_INT(p);
+                r = link_up_down_send_message(rtnl, argv[0], index);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to bring %s interface %s: %m",
+                                               argv[0], FORMAT_IFNAME_FULL(index, FORMAT_IFNAME_IFINDEX));
+        }
+
+        return r;
+}
+
+static int link_delete_send_message(sd_netlink *rtnl, int index) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+        int r;
+
+        assert(rtnl);
+        assert(index >= 0);
+
+        r = sd_rtnl_message_new_link(rtnl, &req, RTM_DELLINK, index);
+        if (r < 0)
+                return rtnl_log_create_error(r);
+
+        r = sd_netlink_call(rtnl, req, 0, NULL);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int link_delete(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        _cleanup_set_free_ Set *indexes = NULL;
+        int index, r;
+        void *p;
+
+        r = sd_netlink_open(&rtnl);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to netlink: %m");
+
+        indexes = set_new(NULL);
+        if (!indexes)
+                return log_oom();
+
+        for (int i = 1; i < argc; i++) {
+                index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
+                if (index < 0)
+                        return index;
+
+                r = set_put(indexes, INT_TO_PTR(index));
+                if (r < 0)
+                        return log_oom();
+        }
+
+        SET_FOREACH(p, indexes) {
+                index = PTR_TO_INT(p);
+                r = link_delete_send_message(rtnl, index);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to delete interface %s: %m",
+                                               FORMAT_IFNAME_FULL(index, FORMAT_IFNAME_IFINDEX));
+        }
+
+        return r;
+}
+
+static int link_renew_one(sd_bus *bus, int index, const char *name) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(bus);
+        assert(index >= 0);
+        assert(name);
+
+        r = bus_call_method(bus, bus_network_mgr, "RenewLink", &error, NULL, "i", index);
+        if (r < 0)
+                return log_error_errno(r, "Failed to renew dynamic configuration of interface %s: %s",
+                                       name, bus_error_message(&error, r));
+
+        return 0;
+}
+
+int link_renew(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        int r;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
+
+        r = 0;
+
+        for (int i = 1; i < argc; i++) {
+                int index;
+
+                index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
+                if (index < 0)
+                        return index;
+
+                RET_GATHER(r, link_renew_one(bus, index, argv[i]));
+        }
+
+        return r;
+}
+
+static int link_force_renew_one(sd_bus *bus, int index, const char *name) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(bus);
+        assert(index >= 0);
+        assert(name);
+
+        r = bus_call_method(bus, bus_network_mgr, "ForceRenewLink", &error, NULL, "i", index);
+        if (r < 0)
+                return log_error_errno(r, "Failed to force renew dynamic configuration of interface %s: %s",
+                                       name, bus_error_message(&error, r));
+
+        return 0;
+}
+
+int link_force_renew(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        int k = 0, r;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
+
+        for (int i = 1; i < argc; i++) {
+                int index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
+                if (index < 0)
+                        return index;
+
+                r = link_force_renew_one(bus, index, argv[i]);
+                if (r < 0 && k >= 0)
+                        k = r;
+        }
+
+        return k;
+}
+
+int verb_reload(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
+
+        r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to reload network settings: %s", bus_error_message(&error, r));
+
+        return 0;
+}
+
+int verb_reconfigure(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        _cleanup_set_free_ Set *indexes = NULL;
+        int index, r;
+        void *p;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
+
+        indexes = set_new(NULL);
+        if (!indexes)
+                return log_oom();
+
+        for (int i = 1; i < argc; i++) {
+                index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
+                if (index < 0)
+                        return index;
+
+                r = set_put(indexes, INT_TO_PTR(index));
+                if (r < 0)
+                        return log_oom();
+        }
+
+        SET_FOREACH(p, indexes) {
+                index = PTR_TO_INT(p);
+                r = bus_call_method(bus, bus_network_mgr, "ReconfigureLink", &error, NULL, "i", index);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to reconfigure network interface %s: %s",
+                                               FORMAT_IFNAME_FULL(index, FORMAT_IFNAME_IFINDEX),
+                                               bus_error_message(&error, r));
+        }
+
+        return 0;
+}
+
+int verb_persistent_storage(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
+        bool ready;
+        int r;
+
+        r = parse_boolean(argv[1]);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse argument: %s", argv[1]);
+        ready = r;
+
+        r = varlink_connect_networkd(&vl);
+        if (r < 0)
+                return r;
+
+        if (ready) {
+                _cleanup_close_ int fd = -EBADF;
+
+                fd = open("/var/lib/systemd/network/", O_CLOEXEC | O_DIRECTORY);
+                if (fd < 0)
+                        return log_error_errno(errno, "Failed to open /var/lib/systemd/network/: %m");
+
+                r = sd_varlink_push_fd(vl, fd);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to push file descriptor of /var/lib/systemd/network/ into varlink: %m");
+
+                TAKE_FD(fd);
+        }
+
+        return varlink_callbo_and_log(
+                        vl,
+                        "io.systemd.Network.SetPersistentStorage",
+                        /* reply= */ NULL,
+                        SD_JSON_BUILD_PAIR_BOOLEAN("Ready", ready));
+}
diff --git a/src/network/networkctl-misc.h b/src/network/networkctl-misc.h
new file mode 100644 (file)
index 0000000..a7cae1c
--- /dev/null
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int link_up_down(int argc, char *argv[], void *userdata);
+int link_delete(int argc, char *argv[], void *userdata);
+int link_renew(int argc, char *argv[], void *userdata);
+int link_force_renew(int argc, char *argv[], void *userdata);
+int verb_reload(int argc, char *argv[], void *userdata);
+int verb_reconfigure(int argc, char *argv[], void *userdata);
+int verb_persistent_storage(int argc, char *argv[], void *userdata);
diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c
new file mode 100644 (file)
index 0000000..14c7771
--- /dev/null
@@ -0,0 +1,970 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* Make sure the net/if.h header is included before any linux/ one */
+#include <net/if.h>
+
+#include "sd-network.h"
+
+#include "bond-util.h"
+#include "bridge-util.h"
+#include "bus-error.h"
+#include "bus-util.h"
+#include "escape.h"
+#include "format-util.h"
+#include "geneve-util.h"
+#include "glyph-util.h"
+#include "ipvlan-util.h"
+#include "macvlan-util.h"
+#include "netif-util.h"
+#include "network-internal.h"
+#include "networkctl.h"
+#include "networkctl-description.h"
+#include "networkctl-dump-util.h"
+#include "networkctl-journal.h"
+#include "networkctl-link-info.h"
+#include "networkctl-lldp.h"
+#include "networkctl-status-link.h"
+#include "networkctl-status-system.h"
+#include "networkctl-util.h"
+#include "strv.h"
+#include "udev-util.h"
+
+static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const LinkInfo *link) {
+        _cleanup_strv_free_ char **buf = NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(table);
+        assert(prefix);
+        assert(bus);
+        assert(link);
+
+        r = link_get_property(bus, link->ifindex, &error, &reply, "org.freedesktop.network1.DHCPServer", "Leases", "a(uayayayayt)");
+        if (r < 0) {
+                bool quiet = sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY);
+
+                log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING,
+                               r, "Failed to query link DHCP leases: %s", bus_error_message(&error, r));
+                return 0;
+        }
+
+        r = sd_bus_message_enter_container(reply, 'a', "(uayayayayt)");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        while ((r = sd_bus_message_enter_container(reply, 'r', "uayayayayt")) > 0) {
+                _cleanup_free_ char *id = NULL, *ip = NULL;
+                const void *client_id, *addr, *gtw, *hwaddr;
+                size_t client_id_sz, sz;
+                uint64_t expiration;
+                uint32_t family;
+
+                r = sd_bus_message_read(reply, "u", &family);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = sd_bus_message_read_array(reply, 'y', &client_id, &client_id_sz);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = sd_bus_message_read_array(reply, 'y', &addr, &sz);
+                if (r < 0 || sz != 4)
+                        return bus_log_parse_error(r);
+
+                r = sd_bus_message_read_array(reply, 'y', &gtw, &sz);
+                if (r < 0 || sz != 4)
+                        return bus_log_parse_error(r);
+
+                r = sd_bus_message_read_array(reply, 'y', &hwaddr, &sz);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = sd_bus_message_read_basic(reply, 't', &expiration);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = sd_dhcp_client_id_to_string_from_raw(client_id, client_id_sz, &id);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = in_addr_to_string(family, addr, &ip);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = strv_extendf(&buf, "%s (to %s)", ip, id);
+                if (r < 0)
+                        return log_oom();
+
+                r = sd_bus_message_exit_container(reply);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+        }
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        if (strv_isempty(buf)) {
+                r = strv_extendf(&buf, "none");
+                if (r < 0)
+                        return log_oom();
+        }
+
+        return dump_list(table, prefix, buf);
+}
+
+static int dump_ifindexes(Table *table, const char *prefix, const int *ifindexes) {
+        int r;
+
+        assert(table);
+        assert(prefix);
+
+        if (!ifindexes)
+                return 0;
+
+        for (unsigned c = 0; ifindexes[c] > 0; c++) {
+                if (c == 0)
+                        r = table_add_cell(table, NULL, TABLE_FIELD, prefix);
+                else
+                        r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                r = table_add_cell(table, NULL, TABLE_IFINDEX, &ifindexes[c]);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        return 0;
+}
+
+#define DUMP_STATS_ONE(name, val_name)                                          \
+        ({                                                                      \
+                r = table_add_cell(table, NULL, TABLE_FIELD, name);             \
+                if (r < 0)                                                      \
+                        return table_log_add_error(r);                          \
+                r = table_add_cell(table, NULL,                                 \
+                                   info->has_stats64 ? TABLE_UINT64 : TABLE_UINT32, \
+                                   info->has_stats64 ? (void*) &info->stats64.val_name : (void*) &info->stats.val_name); \
+                if (r < 0)                                                      \
+                        return table_log_add_error(r);                          \
+        })
+
+static int dump_statistics(Table *table, const LinkInfo *info) {
+        int r;
+
+        assert(table);
+        assert(info);
+
+        if (!arg_stats)
+                return 0;
+
+        if (!info->has_stats64 && !info->has_stats)
+                return 0;
+
+        DUMP_STATS_ONE("Rx Packets", rx_packets);
+        DUMP_STATS_ONE("Tx Packets", tx_packets);
+        DUMP_STATS_ONE("Rx Bytes", rx_bytes);
+        DUMP_STATS_ONE("Tx Bytes", tx_bytes);
+        DUMP_STATS_ONE("Rx Errors", rx_errors);
+        DUMP_STATS_ONE("Tx Errors", tx_errors);
+        DUMP_STATS_ONE("Rx Dropped", rx_dropped);
+        DUMP_STATS_ONE("Tx Dropped", tx_dropped);
+        DUMP_STATS_ONE("Multicast Packets", multicast);
+        DUMP_STATS_ONE("Collisions", collisions);
+
+        return 0;
+}
+
+static int dump_hw_address(Table *table, sd_hwdb *hwdb, const char *field, const struct hw_addr_data *addr) {
+        _cleanup_free_ char *description = NULL;
+        int r;
+
+        assert(table);
+        assert(field);
+        assert(addr);
+
+        if (addr->length == ETH_ALEN)
+                (void) ieee_oui(hwdb, &addr->ether, &description);
+
+        r = table_add_cell(table, NULL, TABLE_FIELD, field);
+        if (r < 0)
+                return table_log_add_error(r);
+
+        r = table_add_cell_stringf(table, NULL, "%s%s%s%s",
+                                   HW_ADDR_TO_STR(addr),
+                                   description ? " (" : "",
+                                   strempty(description),
+                                   description ? ")" : "");
+        if (r < 0)
+                return table_log_add_error(r);
+
+        return 0;
+}
+
+static int table_add_string_line(Table *table, const char *key, const char *value) {
+        int r;
+
+        assert(table);
+        assert(key);
+
+        if (isempty(value))
+                return 0;
+
+        r = table_add_many(table,
+                           TABLE_FIELD, key,
+                           TABLE_STRING, value);
+        if (r < 0)
+                return table_log_add_error(r);
+
+        return 0;
+}
+
+static int format_dropins(char **dropins) {
+        STRV_FOREACH(d, dropins) {
+                _cleanup_free_ char *s = NULL;
+                int glyph = *(d + 1) == NULL ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH;
+
+                s = strjoin(special_glyph(glyph), *d);
+                if (!s)
+                        return log_oom();
+
+                free_and_replace(*d, s);
+        }
+
+        return 0;
+}
+
+static int link_status_one(
+                sd_bus *bus,
+                sd_netlink *rtnl,
+                sd_hwdb *hwdb,
+                sd_varlink *vl,
+                const LinkInfo *info) {
+
+        _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **sip = NULL, **search_domains = NULL,
+                **route_domains = NULL, **link_dropins = NULL, **network_dropins = NULL;
+        _cleanup_free_ char *t = NULL, *network = NULL, *iaid = NULL, *duid = NULL, *captive_portal = NULL,
+                *setup_state = NULL, *operational_state = NULL, *online_state = NULL, *activation_policy = NULL;
+        const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL,
+                *on_color_operational, *off_color_operational, *on_color_setup, *off_color_setup, *on_color_online;
+        _cleanup_free_ int *carrier_bound_to = NULL, *carrier_bound_by = NULL;
+        _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        int r;
+
+        assert(bus);
+        assert(rtnl);
+        assert(vl);
+        assert(info);
+
+        (void) sd_network_link_get_operational_state(info->ifindex, &operational_state);
+        operational_state_to_color(info->name, operational_state, &on_color_operational, &off_color_operational);
+
+        (void) sd_network_link_get_online_state(info->ifindex, &online_state);
+        online_state_to_color(online_state, &on_color_online, NULL);
+
+        (void) sd_network_link_get_setup_state(info->ifindex, &setup_state);
+        setup_state_to_color(setup_state, &on_color_setup, &off_color_setup);
+
+        (void) sd_network_link_get_dns(info->ifindex, &dns);
+        (void) sd_network_link_get_search_domains(info->ifindex, &search_domains);
+        (void) sd_network_link_get_route_domains(info->ifindex, &route_domains);
+        (void) sd_network_link_get_ntp(info->ifindex, &ntp);
+        (void) sd_network_link_get_sip(info->ifindex, &sip);
+        (void) sd_network_link_get_captive_portal(info->ifindex, &captive_portal);
+        (void) sd_network_link_get_network_file(info->ifindex, &network);
+        (void) sd_network_link_get_network_file_dropins(info->ifindex, &network_dropins);
+        (void) sd_network_link_get_carrier_bound_to(info->ifindex, &carrier_bound_to);
+        (void) sd_network_link_get_carrier_bound_by(info->ifindex, &carrier_bound_by);
+        (void) sd_network_link_get_activation_policy(info->ifindex, &activation_policy);
+
+        if (info->sd_device) {
+                const char *joined;
+
+                (void) sd_device_get_property_value(info->sd_device, "ID_NET_LINK_FILE", &link);
+
+                if (sd_device_get_property_value(info->sd_device, "ID_NET_LINK_FILE_DROPINS", &joined) >= 0) {
+                        r = strv_split_full(&link_dropins, joined, ":", EXTRACT_CUNESCAPE);
+                        if (r < 0)
+                                return r;
+                }
+
+                (void) sd_device_get_property_value(info->sd_device, "ID_NET_DRIVER", &driver);
+                (void) sd_device_get_property_value(info->sd_device, "ID_PATH", &path);
+                (void) device_get_vendor_string(info->sd_device, &vendor);
+                (void) device_get_model_string(info->sd_device, &model);
+        }
+
+        r = net_get_type_string(info->sd_device, info->iftype, &t);
+        if (r == -ENOMEM)
+                return log_oom();
+
+        char lease_file[STRLEN("/run/systemd/netif/leases/") + DECIMAL_STR_MAX(int)];
+        xsprintf(lease_file, "/run/systemd/netif/leases/%i", info->ifindex);
+
+        (void) dhcp_lease_load(&lease, lease_file);
+
+        r = format_dropins(network_dropins);
+        if (r < 0)
+                return r;
+
+        if (strv_prepend(&network_dropins, network) < 0)
+                return log_oom();
+
+        r = format_dropins(link_dropins);
+        if (r < 0)
+                return r;
+
+        if (strv_prepend(&link_dropins, link) < 0)
+                return log_oom();
+
+        table = table_new_vertical();
+        if (!table)
+                return log_oom();
+
+        if (arg_full)
+                table_set_width(table, 0);
+
+        /* unit files and basic states. */
+        r = table_add_many(table,
+                           TABLE_FIELD, "Link File",
+                           TABLE_STRV, link_dropins ?: STRV_MAKE("n/a"),
+                           TABLE_FIELD, "Network File",
+                           TABLE_STRV, network_dropins ?: STRV_MAKE("n/a"),
+                           TABLE_FIELD, "State");
+        if (r < 0)
+                return table_log_add_error(r);
+
+        r = table_add_cell_stringf(table, NULL, "%s%s%s (%s%s%s)",
+                                   on_color_operational, strna(operational_state), off_color_operational,
+                                   on_color_setup, setup_state ?: "unmanaged", off_color_setup);
+        if (r < 0)
+                return table_log_add_error(r);
+
+        r = table_add_many(table,
+                           TABLE_FIELD, "Online state",
+                           TABLE_STRING, online_state ?: "unknown",
+                           TABLE_SET_COLOR, on_color_online);
+        if (r < 0)
+                return table_log_add_error(r);
+
+        r = table_add_string_line(table, "Type", t);
+        if (r < 0)
+                return r;
+
+        r = table_add_string_line(table, "Kind", info->netdev_kind);
+        if (r < 0)
+                return r;
+
+        r = table_add_string_line(table, "Path", path);
+        if (r < 0)
+                return r;
+
+        r = table_add_string_line(table, "Driver", driver);
+        if (r < 0)
+                return r;
+
+        r = table_add_string_line(table, "Vendor", vendor);
+        if (r < 0)
+                return r;
+
+        r = table_add_string_line(table, "Model", model);
+        if (r < 0)
+                return r;
+
+        strv_sort(info->alternative_names);
+        r = dump_list(table, "Alternative Names", info->alternative_names);
+        if (r < 0)
+                return r;
+
+        if (info->has_hw_address) {
+                r = dump_hw_address(table, hwdb, "Hardware Address", &info->hw_address);
+                if (r < 0)
+                        return r;
+        }
+
+        if (info->has_permanent_hw_address) {
+                r = dump_hw_address(table, hwdb, "Permanent Hardware Address", &info->permanent_hw_address);
+                if (r < 0)
+                        return r;
+        }
+
+        if (info->mtu > 0) {
+                char min_str[DECIMAL_STR_MAX(uint32_t)], max_str[DECIMAL_STR_MAX(uint32_t)];
+
+                xsprintf(min_str, "%" PRIu32, info->min_mtu);
+                xsprintf(max_str, "%" PRIu32, info->max_mtu);
+
+                r = table_add_cell(table, NULL, TABLE_FIELD, "MTU");
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                r = table_add_cell_stringf(table, NULL, "%" PRIu32 "%s%s%s%s%s%s%s",
+                                           info->mtu,
+                                           info->min_mtu > 0 || info->max_mtu > 0 ? " (" : "",
+                                           info->min_mtu > 0 ? "min: " : "",
+                                           info->min_mtu > 0 ? min_str : "",
+                                           info->min_mtu > 0 && info->max_mtu > 0 ? ", " : "",
+                                           info->max_mtu > 0 ? "max: " : "",
+                                           info->max_mtu > 0 ? max_str : "",
+                                           info->min_mtu > 0 || info->max_mtu > 0 ? ")" : "");
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        r = table_add_string_line(table, "QDisc", info->qdisc);
+        if (r < 0)
+                return r;
+
+        if (info->master > 0) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "Master",
+                                   TABLE_IFINDEX, info->master);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        if (info->has_ipv6_address_generation_mode) {
+                static const struct {
+                        const char *mode;
+                } mode_table[] = {
+                        { "eui64" },
+                        { "none" },
+                        { "stable-privacy" },
+                        { "random" },
+                };
+
+                r = table_add_many(table,
+                                   TABLE_FIELD, "IPv6 Address Generation Mode",
+                                   TABLE_STRING, mode_table[info->addr_gen_mode]);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        if (streq_ptr(info->netdev_kind, "bridge")) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "Forward Delay",
+                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->forward_delay),
+                                   TABLE_FIELD, "Hello Time",
+                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->hello_time),
+                                   TABLE_FIELD, "Max Age",
+                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->max_age),
+                                   TABLE_FIELD, "Ageing Time",
+                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->ageing_time),
+                                   TABLE_FIELD, "Priority",
+                                   TABLE_UINT16, info->priority,
+                                   TABLE_FIELD, "STP",
+                                   TABLE_BOOLEAN, info->stp_state > 0,
+                                   TABLE_FIELD, "Multicast IGMP Version",
+                                   TABLE_UINT8, info->mcast_igmp_version,
+                                   TABLE_FIELD, "Cost",
+                                   TABLE_UINT32, info->cost);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                if (info->has_fdb_learned) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "FDB Learned",
+                                           TABLE_UINT32, info->fdb_n_learned,
+                                           TABLE_FIELD, "FDB Max Learned",
+                                           TABLE_UINT32, info->fdb_max_learned);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (info->port_state <= BR_STATE_BLOCKING) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Port State",
+                                           TABLE_STRING, bridge_state_to_string(info->port_state));
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+        } else if (streq_ptr(info->netdev_kind, "bond")) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "Mode",
+                                   TABLE_STRING, bond_mode_to_string(info->mode),
+                                   TABLE_FIELD, "Miimon",
+                                   TABLE_TIMESPAN_MSEC, info->miimon * USEC_PER_MSEC,
+                                   TABLE_FIELD, "Updelay",
+                                   TABLE_TIMESPAN_MSEC, info->updelay * USEC_PER_MSEC,
+                                   TABLE_FIELD, "Downdelay",
+                                   TABLE_TIMESPAN_MSEC, info->downdelay * USEC_PER_MSEC);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+        } else if (streq_ptr(info->netdev_kind, "vxlan")) {
+                char ttl[CONST_MAX(STRLEN("auto") + 1, DECIMAL_STR_MAX(uint8_t))];
+
+                if (info->vxlan_info.vni > 0) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "VNI",
+                                           TABLE_UINT32, info->vxlan_info.vni);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (IN_SET(info->vxlan_info.group_family, AF_INET, AF_INET6)) {
+                        const char *p;
+
+                        r = in_addr_is_multicast(info->vxlan_info.group_family, &info->vxlan_info.group);
+                        if (r <= 0)
+                                p = "Remote";
+                        else
+                                p = "Group";
+
+                        r = table_add_many(table,
+                                           TABLE_FIELD, p,
+                                           info->vxlan_info.group_family == AF_INET ? TABLE_IN_ADDR : TABLE_IN6_ADDR, &info->vxlan_info.group);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (IN_SET(info->vxlan_info.local_family, AF_INET, AF_INET6)) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Local",
+                                           info->vxlan_info.local_family == AF_INET ? TABLE_IN_ADDR : TABLE_IN6_ADDR, &info->vxlan_info.local);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (info->vxlan_info.dest_port > 0) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Destination Port",
+                                           TABLE_UINT16, be16toh(info->vxlan_info.dest_port));
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (info->vxlan_info.link > 0) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Underlying Device",
+                                           TABLE_IFINDEX, info->vxlan_info.link);
+                        if (r < 0)
+                                 return table_log_add_error(r);
+                }
+
+                r = table_add_many(table,
+                                   TABLE_FIELD, "Learning",
+                                   TABLE_BOOLEAN, info->vxlan_info.learning,
+                                   TABLE_FIELD, "RSC",
+                                   TABLE_BOOLEAN, info->vxlan_info.rsc,
+                                   TABLE_FIELD, "L3MISS",
+                                   TABLE_BOOLEAN, info->vxlan_info.l3miss,
+                                   TABLE_FIELD, "L2MISS",
+                                   TABLE_BOOLEAN, info->vxlan_info.l2miss);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                if (info->vxlan_info.tos > 1) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "TOS",
+                                           TABLE_UINT8, info->vxlan_info.tos);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (info->vxlan_info.ttl > 0)
+                        xsprintf(ttl, "%" PRIu8, info->vxlan_info.ttl);
+                else
+                        strcpy(ttl, "auto");
+
+                r = table_add_many(table,
+                                   TABLE_FIELD, "TTL",
+                                   TABLE_STRING, ttl);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+        } else if (streq_ptr(info->netdev_kind, "vlan") && info->vlan_id > 0) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "VLan Id",
+                                   TABLE_UINT16, info->vlan_id);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+        } else if (STRPTR_IN_SET(info->netdev_kind, "ipip", "sit", "gre", "gretap", "erspan", "vti")) {
+                if (in_addr_is_set(AF_INET, &info->local)) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Local",
+                                           TABLE_IN_ADDR, &info->local);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (in_addr_is_set(AF_INET, &info->remote)) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Remote",
+                                           TABLE_IN_ADDR, &info->remote);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+        } else if (STRPTR_IN_SET(info->netdev_kind, "ip6gre", "ip6gretap", "ip6erspan", "vti6")) {
+                if (in_addr_is_set(AF_INET6, &info->local)) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Local",
+                                           TABLE_IN6_ADDR, &info->local);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (in_addr_is_set(AF_INET6, &info->remote)) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Remote",
+                                           TABLE_IN6_ADDR, &info->remote);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+        } else if (streq_ptr(info->netdev_kind, "geneve")) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "VNI",
+                                   TABLE_UINT32, info->vni);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                if (info->has_tunnel_ipv4 && in_addr_is_set(AF_INET, &info->remote)) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Remote",
+                                           TABLE_IN_ADDR, &info->remote);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                } else if (in_addr_is_set(AF_INET6, &info->remote)) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Remote",
+                                           TABLE_IN6_ADDR, &info->remote);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (info->ttl > 0) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "TTL",
+                                           TABLE_UINT8, info->ttl);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (info->tos > 0) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "TOS",
+                                           TABLE_UINT8, info->tos);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                r = table_add_many(table,
+                                   TABLE_FIELD, "Port",
+                                   TABLE_UINT16, info->tunnel_port,
+                                   TABLE_FIELD, "Inherit",
+                                   TABLE_STRING, geneve_df_to_string(info->inherit));
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                if (info->df > 0) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "IPDoNotFragment",
+                                           TABLE_UINT8, info->df);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                r = table_add_many(table,
+                                   TABLE_FIELD, "UDPChecksum",
+                                   TABLE_BOOLEAN, info->csum,
+                                   TABLE_FIELD, "UDP6ZeroChecksumTx",
+                                   TABLE_BOOLEAN, info->csum6_tx,
+                                   TABLE_FIELD, "UDP6ZeroChecksumRx",
+                                   TABLE_BOOLEAN, info->csum6_rx);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                if (info->label > 0) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "FlowLabel",
+                                           TABLE_UINT32, info->label);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+        } else if (STRPTR_IN_SET(info->netdev_kind, "macvlan", "macvtap")) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "Mode",
+                                   TABLE_STRING, macvlan_mode_to_string(info->macvlan_mode));
+                if (r < 0)
+                        return table_log_add_error(r);
+
+        } else if (streq_ptr(info->netdev_kind, "ipvlan")) {
+                const char *p;
+
+                if (info->ipvlan_flags & IPVLAN_F_PRIVATE)
+                        p = "private";
+                else if (info->ipvlan_flags & IPVLAN_F_VEPA)
+                        p = "vepa";
+                else
+                        p = "bridge";
+
+                r = table_add_cell(table, NULL, TABLE_FIELD, "Mode");
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                r = table_add_cell_stringf(table, NULL, "%s (%s)",
+                                           ipvlan_mode_to_string(info->ipvlan_mode), p);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        if (info->has_wlan_link_info) {
+                _cleanup_free_ char *esc = NULL;
+
+                r = table_add_cell(table, NULL, TABLE_FIELD, "Wi-Fi access point");
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                if (info->ssid)
+                        esc = cescape(info->ssid);
+
+                r = table_add_cell_stringf(table, NULL, "%s (%s)",
+                                           strnull(esc),
+                                           ETHER_ADDR_TO_STR(&info->bssid));
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        if (info->has_bitrates) {
+                r = table_add_cell(table, NULL, TABLE_FIELD, "Bit Rate (Tx/Rx)");
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                r = table_add_cell_stringf(table, NULL, "%sbps/%sbps",
+                                           FORMAT_BYTES_FULL(info->tx_bitrate, 0),
+                                           FORMAT_BYTES_FULL(info->rx_bitrate, 0));
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        if (info->has_tx_queues || info->has_rx_queues) {
+                r = table_add_cell(table, NULL, TABLE_FIELD, "Number of Queues (Tx/Rx)");
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                r = table_add_cell_stringf(table, NULL, "%" PRIu32 "/%" PRIu32, info->tx_queues, info->rx_queues);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        if (info->has_ethtool_link_info) {
+                if (IN_SET(info->autonegotiation, AUTONEG_DISABLE, AUTONEG_ENABLE)) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Auto negotiation",
+                                           TABLE_BOOLEAN, info->autonegotiation == AUTONEG_ENABLE);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                if (info->speed > 0 && info->speed != UINT64_MAX) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Speed",
+                                           TABLE_BPS, info->speed);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                r = table_add_string_line(table, "Duplex", duplex_to_string(info->duplex));
+                if (r < 0)
+                        return r;
+
+                r = table_add_string_line(table, "Port", port_to_string(info->port));
+                if (r < 0)
+                        return r;
+        }
+
+        r = dump_addresses(rtnl, lease, table, info->ifindex);
+        if (r < 0)
+                return r;
+
+        r = dump_gateways(rtnl, hwdb, table, info->ifindex);
+        if (r < 0)
+                return r;
+
+        r = dump_list(table, "DNS", dns);
+        if (r < 0)
+                return r;
+
+        r = dump_list(table, "Search Domains", search_domains);
+        if (r < 0)
+                return r;
+
+        r = dump_list(table, "Route Domains", route_domains);
+        if (r < 0)
+                return r;
+
+        r = dump_list(table, "NTP", ntp);
+        if (r < 0)
+                return r;
+
+        r = dump_list(table, "SIP", sip);
+        if (r < 0)
+                return r;
+
+        r = dump_ifindexes(table, "Carrier Bound To", carrier_bound_to);
+        if (r < 0)
+                return r;
+
+        r = dump_ifindexes(table, "Carrier Bound By", carrier_bound_by);
+        if (r < 0)
+                return r;
+
+        r = table_add_string_line(table, "Activation Policy", activation_policy);
+        if (r < 0)
+                return r;
+
+        r = sd_network_link_get_required_for_online(info->ifindex);
+        if (r >= 0) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "Required For Online",
+                                   TABLE_BOOLEAN, r);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        if (captive_portal) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "Captive Portal",
+                                   TABLE_STRING, captive_portal,
+                                   TABLE_SET_URL, captive_portal);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        if (lease) {
+                const sd_dhcp_client_id *client_id;
+                const char *tz;
+
+                r = sd_dhcp_lease_get_timezone(lease, &tz);
+                if (r >= 0) {
+                        r = table_add_many(table,
+                                           TABLE_FIELD, "Time Zone",
+                                           TABLE_STRING, tz);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+
+                r = sd_dhcp_lease_get_client_id(lease, &client_id);
+                if (r >= 0) {
+                        _cleanup_free_ char *id = NULL;
+
+                        r = sd_dhcp_client_id_to_string(client_id, &id);
+                        if (r >= 0) {
+                                r = table_add_many(table,
+                                                   TABLE_FIELD, "DHCPv4 Client ID",
+                                                   TABLE_STRING, id);
+                                if (r < 0)
+                                        return table_log_add_error(r);
+                        }
+                }
+        }
+
+        r = sd_network_link_get_dhcp6_client_iaid_string(info->ifindex, &iaid);
+        if (r >= 0) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "DHCPv6 Client IAID",
+                                   TABLE_STRING, iaid);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        r = sd_network_link_get_dhcp6_client_duid_string(info->ifindex, &duid);
+        if (r >= 0) {
+                r = table_add_many(table,
+                                   TABLE_FIELD, "DHCPv6 Client DUID",
+                                   TABLE_STRING, duid);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        r = dump_lldp_neighbors(vl, table, info->ifindex);
+        if (r < 0)
+                return r;
+
+        r = dump_dhcp_leases(table, "Offered DHCP leases", bus, info);
+        if (r < 0)
+                return r;
+
+        r = dump_statistics(table, info);
+        if (r < 0)
+                return r;
+
+        /* First line: circle, ifindex, ifname. */
+        printf("%s%s%s %d: %s\n",
+               on_color_operational, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE), off_color_operational,
+               info->ifindex, info->name);
+
+        r = table_print(table, NULL);
+        if (r < 0)
+                return table_log_print_error(r);
+
+        return show_logs(info->ifindex, info->name);
+}
+
+int link_status(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
+        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
+        _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
+        int r, c;
+
+        r = dump_description(argc, argv);
+        if (r != 0)
+                return r;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        pager_open(arg_pager_flags);
+
+        r = sd_netlink_open(&rtnl);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to netlink: %m");
+
+        r = sd_hwdb_new(&hwdb);
+        if (r < 0)
+                log_debug_errno(r, "Failed to open hardware database: %m");
+
+        r = varlink_connect_networkd(&vl);
+        if (r < 0)
+                return r;
+
+        if (arg_all)
+                c = acquire_link_info(bus, rtnl, NULL, &links);
+        else if (argc <= 1)
+                return system_status(rtnl, hwdb);
+        else
+                c = acquire_link_info(bus, rtnl, argv + 1, &links);
+        if (c < 0)
+                return c;
+
+        r = 0;
+
+        bool first = true;
+        FOREACH_ARRAY(i, links, c) {
+                if (!first)
+                        putchar('\n');
+
+                RET_GATHER(r, link_status_one(bus, rtnl, hwdb, vl, i));
+
+                first = false;
+        }
+
+        return r;
+}
diff --git a/src/network/networkctl-status-link.h b/src/network/networkctl-status-link.h
new file mode 100644 (file)
index 0000000..1c1b4ea
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int link_status(int argc, char *argv[], void *userdata);
diff --git a/src/network/networkctl-status-system.c b/src/network/networkctl-status-system.c
new file mode 100644 (file)
index 0000000..225fa7e
--- /dev/null
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-network.h"
+
+#include "fs-util.h"
+#include "glyph-util.h"
+#include "networkctl.h"
+#include "networkctl-dump-util.h"
+#include "networkctl-journal.h"
+#include "networkctl-status-system.h"
+#include "networkctl-util.h"
+#include "strv.h"
+
+int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) {
+        _cleanup_free_ char *operational_state = NULL, *online_state = NULL, *netifs_joined = NULL;
+        _cleanup_strv_free_ char **netifs = NULL, **dns = NULL, **ntp = NULL, **search_domains = NULL, **route_domains = NULL;
+        const char *on_color_operational, *off_color_operational, *on_color_online;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        int r;
+
+        assert(rtnl);
+
+        (void) sd_network_get_operational_state(&operational_state);
+        operational_state_to_color(NULL, operational_state, &on_color_operational, &off_color_operational);
+
+        (void) sd_network_get_online_state(&online_state);
+        online_state_to_color(online_state, &on_color_online, NULL);
+
+        table = table_new_vertical();
+        if (!table)
+                return log_oom();
+
+        if (arg_full)
+                table_set_width(table, 0);
+
+        r = get_files_in_directory("/run/systemd/netif/links/", &netifs);
+        if (r < 0 && r != -ENOENT)
+                return log_error_errno(r, "Failed to list network interfaces: %m");
+        else if (r > 0) {
+                netifs_joined = strv_join(netifs, ", ");
+                if (!netifs_joined)
+                        return log_oom();
+        }
+
+        r = table_add_many(table,
+                           TABLE_FIELD, "State",
+                           TABLE_STRING, strna(operational_state),
+                           TABLE_SET_COLOR, on_color_operational,
+                           TABLE_FIELD, "Online state",
+                           TABLE_STRING, online_state ?: "unknown",
+                           TABLE_SET_COLOR, on_color_online);
+        if (r < 0)
+                return table_log_add_error(r);
+
+        r = dump_addresses(rtnl, NULL, table, 0);
+        if (r < 0)
+                return r;
+
+        r = dump_gateways(rtnl, hwdb, table, 0);
+        if (r < 0)
+                return r;
+
+        (void) sd_network_get_dns(&dns);
+        r = dump_list(table, "DNS", dns);
+        if (r < 0)
+                return r;
+
+        (void) sd_network_get_search_domains(&search_domains);
+        r = dump_list(table, "Search Domains", search_domains);
+        if (r < 0)
+                return r;
+
+        (void) sd_network_get_route_domains(&route_domains);
+        r = dump_list(table, "Route Domains", route_domains);
+        if (r < 0)
+                return r;
+
+        (void) sd_network_get_ntp(&ntp);
+        r = dump_list(table, "NTP", ntp);
+        if (r < 0)
+                return r;
+
+        printf("%s%s%s Interfaces: %s\n",
+               on_color_operational, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE), off_color_operational,
+               strna(netifs_joined));
+
+        r = table_print(table, NULL);
+        if (r < 0)
+                return table_log_print_error(r);
+
+        return show_logs(0, NULL);
+}
diff --git a/src/network/networkctl-status-system.h b/src/network/networkctl-status-system.h
new file mode 100644 (file)
index 0000000..8d9da17
--- /dev/null
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-hwdb.h"
+#include "sd-netlink.h"
+
+int system_status(sd_netlink *rtnl, sd_hwdb *hwdb);
diff --git a/src/network/networkctl-util.c b/src/network/networkctl-util.c
new file mode 100644 (file)
index 0000000..88620aa
--- /dev/null
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "ansi-color.h"
+#include "networkctl.h"
+#include "networkctl-util.h"
+#include "strv.h"
+#include "varlink-util.h"
+
+int varlink_connect_networkd(sd_varlink **ret_varlink) {
+        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
+        sd_json_variant *reply;
+        uint64_t id;
+        int r;
+
+        r = sd_varlink_connect_address(&vl, "/run/systemd/netif/io.systemd.Network");
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to network service /run/systemd/netif/io.systemd.Network: %m");
+
+        (void) sd_varlink_set_description(vl, "varlink-network");
+
+        r = sd_varlink_set_allow_fd_passing_output(vl, true);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allow passing file descriptor through varlink: %m");
+
+        r = varlink_call_and_log(vl, "io.systemd.Network.GetNamespaceId", /* parameters= */ NULL, &reply);
+        if (r < 0)
+                return r;
+
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "NamespaceId", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, 0, SD_JSON_MANDATORY },
+                {},
+        };
+
+        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &id);
+        if (r < 0)
+                return r;
+
+        if (id == 0)
+                log_debug("systemd-networkd.service not running in a network namespace (?), skipping netns check.");
+        else {
+                struct stat st;
+
+                if (stat("/proc/self/ns/net", &st) < 0)
+                        return log_error_errno(errno, "Failed to determine our own network namespace ID: %m");
+
+                if (id != st.st_ino)
+                        return log_error_errno(SYNTHETIC_ERRNO(EREMOTE),
+                                               "networkctl must be invoked in same network namespace as systemd-networkd.service.");
+        }
+
+        if (ret_varlink)
+                *ret_varlink = TAKE_PTR(vl);
+        return 0;
+}
+
+bool networkd_is_running(void) {
+        static int cached = -1;
+        int r;
+
+        if (cached < 0) {
+                r = access("/run/systemd/netif/state", F_OK);
+                if (r < 0) {
+                        if (errno != ENOENT)
+                                log_debug_errno(errno,
+                                                "Failed to determine whether networkd is running, assuming it's not: %m");
+
+                        cached = false;
+                } else
+                        cached = true;
+        }
+
+        return cached;
+}
+
+int acquire_bus(sd_bus **ret) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r;
+
+        assert(ret);
+
+        r = sd_bus_open_system(&bus);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to system bus: %m");
+
+        (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
+
+        if (networkd_is_running()) {
+                r = varlink_connect_networkd(/* ret_varlink = */ NULL);
+                if (r < 0)
+                        return r;
+        } else
+                log_warning("systemd-networkd is not running, output might be incomplete.");
+
+        *ret = TAKE_PTR(bus);
+        return 0;
+}
+
+int link_get_property(
+                sd_bus *bus,
+                int ifindex,
+                sd_bus_error *error,
+                sd_bus_message **reply,
+                const char *iface,
+                const char *propname,
+                const char *type) {
+
+        _cleanup_free_ char *path = NULL;
+        char ifindex_str[DECIMAL_STR_MAX(int)];
+        int r;
+
+        assert(bus);
+        assert(ifindex >= 0);
+        assert(error);
+        assert(reply);
+        assert(iface);
+        assert(propname);
+        assert(type);
+
+        xsprintf(ifindex_str, "%i", ifindex);
+
+        r = sd_bus_path_encode("/org/freedesktop/network1/link", ifindex_str, &path);
+        if (r < 0)
+                return r;
+
+        return sd_bus_get_property(bus, "org.freedesktop.network1", path, iface, propname, error, reply, type);
+}
+
+void operational_state_to_color(const char *name, const char *state, const char **on, const char **off) {
+        if (STRPTR_IN_SET(state, "routable", "enslaved") ||
+            (streq_ptr(name, "lo") && streq_ptr(state, "carrier"))) {
+                if (on)
+                        *on = ansi_highlight_green();
+                if (off)
+                        *off = ansi_normal();
+        } else if (streq_ptr(state, "degraded")) {
+                if (on)
+                        *on = ansi_highlight_yellow();
+                if (off)
+                        *off = ansi_normal();
+        } else {
+                if (on)
+                        *on = "";
+                if (off)
+                        *off = "";
+        }
+}
+
+void setup_state_to_color(const char *state, const char **on, const char **off) {
+        if (streq_ptr(state, "configured")) {
+                if (on)
+                        *on = ansi_highlight_green();
+                if (off)
+                        *off = ansi_normal();
+        } else if (streq_ptr(state, "configuring")) {
+                if (on)
+                        *on = ansi_highlight_yellow();
+                if (off)
+                        *off = ansi_normal();
+        } else if (STRPTR_IN_SET(state, "failed", "linger")) {
+                if (on)
+                        *on = ansi_highlight_red();
+                if (off)
+                        *off = ansi_normal();
+        } else {
+                if (on)
+                        *on = "";
+                if (off)
+                        *off = "";
+        }
+}
+
+void online_state_to_color(const char *state, const char **on, const char **off) {
+        if (streq_ptr(state, "online")) {
+                if (on)
+                        *on = ansi_highlight_green();
+                if (off)
+                        *off = ansi_normal();
+        } else if (streq_ptr(state, "partial")) {
+                if (on)
+                        *on = ansi_highlight_yellow();
+                if (off)
+                        *off = ansi_normal();
+        } else {
+                if (on)
+                        *on = "";
+                if (off)
+                        *off = "";
+        }
+}
diff --git a/src/network/networkctl-util.h b/src/network/networkctl-util.h
new file mode 100644 (file)
index 0000000..1079215
--- /dev/null
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "sd-bus.h"
+#include "sd-varlink.h"
+
+int varlink_connect_networkd(sd_varlink **ret_varlink);
+bool networkd_is_running(void);
+int acquire_bus(sd_bus **ret);
+int link_get_property(
+                sd_bus *bus,
+                int ifindex,
+                sd_bus_error *error,
+                sd_bus_message **reply,
+                const char *iface,
+                const char *propname,
+                const char *type);
+
+void operational_state_to_color(const char *name, const char *state, const char **on, const char **off);
+void setup_state_to_color(const char *state, const char **on, const char **off);
+void online_state_to_color(const char *state, const char **on, const char **off);
index 854f73045f16df7916ac368c8d0059d97372dc4c..058d801a6c8c34b120c87f7c1b190994bd144945 100644 (file)
@@ -1,88 +1,23 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-/* Make sure the net/if.h header is included before any linux/ one */
-#include <net/if.h>
-#include <arpa/inet.h>
 #include <getopt.h>
-#include <linux/if_addrlabel.h>
-#include <stdbool.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <linux/if_bridge.h>
-#include <linux/if_tunnel.h>
 
-#include "sd-bus.h"
-#include "sd-device.h"
-#include "sd-dhcp-client.h"
-#include "sd-hwdb.h"
-#include "sd-json.h"
-#include "sd-netlink.h"
-#include "sd-network.h"
-#include "sd-varlink.h"
-
-#include "alloc-util.h"
-#include "bond-util.h"
-#include "bridge-util.h"
 #include "build.h"
-#include "bus-common-errors.h"
-#include "bus-error.h"
-#include "bus-locator.h"
-#include "device-util.h"
-#include "escape.h"
-#include "ether-addr-util.h"
-#include "ethtool-util.h"
-#include "fd-util.h"
-#include "format-table.h"
-#include "format-util.h"
-#include "fs-util.h"
-#include "geneve-util.h"
-#include "glob-util.h"
-#include "hwdb-util.h"
-#include "ipvlan-util.h"
-#include "journal-internal.h"
-#include "json-util.h"
-#include "local-addresses.h"
-#include "locale-util.h"
-#include "logs-show.h"
-#include "macro.h"
-#include "macvlan-util.h"
 #include "main-func.h"
-#include "netif-util.h"
-#include "netlink-util.h"
-#include "network-internal.h"
-#include "network-util.h"
 #include "networkctl.h"
+#include "networkctl-address-label.h"
 #include "networkctl-config-file.h"
-#include "pager.h"
+#include "networkctl-list.h"
+#include "networkctl-lldp.h"
+#include "networkctl-misc.h"
+#include "networkctl-status-link.h"
+#include "networkctl-util.h"
 #include "parse-argument.h"
 #include "parse-util.h"
-#include "path-lookup.h"
 #include "path-util.h"
 #include "pretty-print.h"
-#include "set.h"
 #include "sigbus.h"
-#include "socket-netlink.h"
-#include "socket-util.h"
-#include "sort-util.h"
-#include "sparse-endian.h"
-#include "stdio-util.h"
-#include "string-table.h"
-#include "string-util.h"
-#include "strv.h"
-#include "strxcpyx.h"
-#include "terminal-util.h"
-#include "udev-util.h"
-#include "unit-def.h"
-#include "varlink-util.h"
 #include "verbs.h"
-#include "wifi-util.h"
-
-/* Kernel defines MODULE_NAME_LEN as 64 - sizeof(unsigned long). So, 64 is enough. */
-#define NETDEV_KIND_MAX 64
-
-/* use 128 kB for receive socket kernel queue, we shouldn't need more here */
-#define RCVBUF_SIZE    (128*1024)
 
 PagerFlags arg_pager_flags = 0;
 bool arg_legend = true;
@@ -99,2901 +34,6 @@ bool arg_ask_password = true;
 
 STATIC_DESTRUCTOR_REGISTER(arg_drop_in, freep);
 
-static int varlink_connect_networkd(sd_varlink **ret_varlink) {
-        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
-        sd_json_variant *reply;
-        uint64_t id;
-        int r;
-
-        r = sd_varlink_connect_address(&vl, "/run/systemd/netif/io.systemd.Network");
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to network service /run/systemd/netif/io.systemd.Network: %m");
-
-        (void) sd_varlink_set_description(vl, "varlink-network");
-
-        r = sd_varlink_set_allow_fd_passing_output(vl, true);
-        if (r < 0)
-                return log_error_errno(r, "Failed to allow passing file descriptor through varlink: %m");
-
-        r = varlink_call_and_log(vl, "io.systemd.Network.GetNamespaceId", /* parameters= */ NULL, &reply);
-        if (r < 0)
-                return r;
-
-        static const sd_json_dispatch_field dispatch_table[] = {
-                { "NamespaceId", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, 0, SD_JSON_MANDATORY },
-                {},
-        };
-
-        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &id);
-        if (r < 0)
-                return r;
-
-        if (id == 0)
-                log_debug("systemd-networkd.service not running in a network namespace (?), skipping netns check.");
-        else {
-                struct stat st;
-
-                if (stat("/proc/self/ns/net", &st) < 0)
-                        return log_error_errno(errno, "Failed to determine our own network namespace ID: %m");
-
-                if (id != st.st_ino)
-                        return log_error_errno(SYNTHETIC_ERRNO(EREMOTE),
-                                               "networkctl must be invoked in same network namespace as systemd-networkd.service.");
-        }
-
-        if (ret_varlink)
-                *ret_varlink = TAKE_PTR(vl);
-        return 0;
-}
-
-bool networkd_is_running(void) {
-        static int cached = -1;
-        int r;
-
-        if (cached < 0) {
-                r = access("/run/systemd/netif/state", F_OK);
-                if (r < 0) {
-                        if (errno != ENOENT)
-                                log_debug_errno(errno,
-                                                "Failed to determine whether networkd is running, assuming it's not: %m");
-
-                        cached = false;
-                } else
-                        cached = true;
-        }
-
-        return cached;
-}
-
-int acquire_bus(sd_bus **ret) {
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        int r;
-
-        assert(ret);
-
-        r = sd_bus_open_system(&bus);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to system bus: %m");
-
-        (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
-
-        if (networkd_is_running()) {
-                r = varlink_connect_networkd(/* ret_varlink = */ NULL);
-                if (r < 0)
-                        return r;
-        } else
-                log_warning("systemd-networkd is not running, output might be incomplete.");
-
-        *ret = TAKE_PTR(bus);
-        return 0;
-}
-
-static int get_description(sd_bus *bus, sd_json_variant **ret) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        const char *text;
-        int r;
-
-        assert(bus);
-        assert(ret);
-
-        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 = sd_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(sd_bus *bus) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
-        int r;
-
-        assert(bus);
-
-        r = get_description(bus, &v);
-        if (r < 0)
-                return r;
-
-        sd_json_variant_dump(v, arg_json_format_flags, NULL, NULL);
-        return 0;
-}
-
-static int dump_link_description(sd_bus *bus, char * const *patterns) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
-        _cleanup_free_ bool *matched_patterns = NULL;
-        sd_json_variant *i;
-        size_t c = 0;
-        int r;
-
-        assert(bus);
-        assert(patterns);
-
-        r = get_description(bus, &v);
-        if (r < 0)
-                return r;
-
-        matched_patterns = new0(bool, strv_length(patterns));
-        if (!matched_patterns)
-                return log_oom();
-
-        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(v, "Interfaces")) {
-                char ifindex_str[DECIMAL_STR_MAX(int64_t)];
-                const char *name;
-                int64_t index;
-                size_t pos;
-
-                name = sd_json_variant_string(sd_json_variant_by_key(i, "Name"));
-                index = sd_json_variant_integer(sd_json_variant_by_key(i, "Index"));
-                xsprintf(ifindex_str, "%" PRIi64, index);
-
-                if (!strv_fnmatch_full(patterns, ifindex_str, 0, &pos) &&
-                    !strv_fnmatch_full(patterns, name, 0, &pos)) {
-                        bool match = false;
-                        sd_json_variant *a;
-
-                        JSON_VARIANT_ARRAY_FOREACH(a, sd_json_variant_by_key(i, "AlternativeNames"))
-                                if (strv_fnmatch_full(patterns, sd_json_variant_string(a), 0, &pos)) {
-                                        match = true;
-                                        break;
-                                }
-
-                        if (!match)
-                                continue;
-                }
-
-                matched_patterns[pos] = true;
-                sd_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") ||
-            (streq_ptr(name, "lo") && streq_ptr(state, "carrier"))) {
-                if (on)
-                        *on = ansi_highlight_green();
-                if (off)
-                        *off = ansi_normal();
-        } else if (streq_ptr(state, "degraded")) {
-                if (on)
-                        *on = ansi_highlight_yellow();
-                if (off)
-                        *off = ansi_normal();
-        } else {
-                if (on)
-                        *on = "";
-                if (off)
-                        *off = "";
-        }
-}
-
-static void setup_state_to_color(const char *state, const char **on, const char **off) {
-        if (streq_ptr(state, "configured")) {
-                if (on)
-                        *on = ansi_highlight_green();
-                if (off)
-                        *off = ansi_normal();
-        } else if (streq_ptr(state, "configuring")) {
-                if (on)
-                        *on = ansi_highlight_yellow();
-                if (off)
-                        *off = ansi_normal();
-        } else if (STRPTR_IN_SET(state, "failed", "linger")) {
-                if (on)
-                        *on = ansi_highlight_red();
-                if (off)
-                        *off = ansi_normal();
-        } else {
-                if (on)
-                        *on = "";
-                if (off)
-                        *off = "";
-        }
-}
-
-static void online_state_to_color(const char *state, const char **on, const char **off) {
-        if (streq_ptr(state, "online")) {
-                if (on)
-                        *on = ansi_highlight_green();
-                if (off)
-                        *off = ansi_normal();
-        } else if (streq_ptr(state, "partial")) {
-                if (on)
-                        *on = ansi_highlight_yellow();
-                if (off)
-                        *off = ansi_normal();
-        } else {
-                if (on)
-                        *on = "";
-                if (off)
-                        *off = "";
-        }
-}
-
-typedef struct VxLanInfo {
-        uint32_t vni;
-        uint32_t link;
-
-        int local_family;
-        int group_family;
-
-        union in_addr_union local;
-        union in_addr_union group;
-
-        uint16_t dest_port;
-
-        uint8_t proxy;
-        uint8_t learning;
-        uint8_t rsc;
-        uint8_t l2miss;
-        uint8_t l3miss;
-        uint8_t tos;
-        uint8_t ttl;
-} VxLanInfo;
-
-typedef struct LinkInfo {
-        char name[IFNAMSIZ+1];
-        char *netdev_kind;
-        sd_device *sd_device;
-        int ifindex;
-        unsigned short iftype;
-        struct hw_addr_data hw_address;
-        struct hw_addr_data permanent_hw_address;
-        uint32_t master;
-        uint32_t mtu;
-        uint32_t min_mtu;
-        uint32_t max_mtu;
-        uint32_t tx_queues;
-        uint32_t rx_queues;
-        uint8_t addr_gen_mode;
-        char *qdisc;
-        char **alternative_names;
-
-        union {
-                struct rtnl_link_stats64 stats64;
-                struct rtnl_link_stats stats;
-        };
-
-        uint64_t tx_bitrate;
-        uint64_t rx_bitrate;
-
-        /* bridge info */
-        uint32_t forward_delay;
-        uint32_t hello_time;
-        uint32_t max_age;
-        uint32_t ageing_time;
-        uint32_t stp_state;
-        uint32_t cost;
-        uint16_t priority;
-        uint8_t mcast_igmp_version;
-        uint8_t port_state;
-        uint32_t fdb_max_learned;
-        uint32_t fdb_n_learned;
-        bool has_fdb_learned;
-
-        /* vxlan info */
-        VxLanInfo vxlan_info;
-
-        /* vlan info */
-        uint16_t vlan_id;
-
-        /* tunnel info */
-        uint8_t ttl;
-        uint8_t tos;
-        uint8_t inherit;
-        uint8_t df;
-        uint8_t csum;
-        uint8_t csum6_tx;
-        uint8_t csum6_rx;
-        uint16_t tunnel_port;
-        uint32_t vni;
-        uint32_t label;
-        union in_addr_union local;
-        union in_addr_union remote;
-
-        /* bonding info */
-        uint8_t mode;
-        uint32_t miimon;
-        uint32_t updelay;
-        uint32_t downdelay;
-
-        /* macvlan and macvtap info */
-        uint32_t macvlan_mode;
-
-        /* ipvlan info */
-        uint16_t ipvlan_mode;
-        uint16_t ipvlan_flags;
-
-        /* ethtool info */
-        int autonegotiation;
-        uint64_t speed;
-        Duplex duplex;
-        NetDevPort port;
-
-        /* wlan info */
-        enum nl80211_iftype wlan_iftype;
-        char *ssid;
-        struct ether_addr bssid;
-
-        bool has_hw_address:1;
-        bool has_permanent_hw_address:1;
-        bool has_tx_queues:1;
-        bool has_rx_queues:1;
-        bool has_stats64:1;
-        bool has_stats:1;
-        bool has_bitrates:1;
-        bool has_ethtool_link_info:1;
-        bool has_wlan_link_info:1;
-        bool has_tunnel_ipv4:1;
-        bool has_ipv6_address_generation_mode:1;
-
-        bool needs_freeing:1;
-} LinkInfo;
-
-static int link_info_compare(const LinkInfo *a, const LinkInfo *b) {
-        return CMP(a->ifindex, b->ifindex);
-}
-
-static LinkInfo* link_info_array_free(LinkInfo *array) {
-        for (unsigned i = 0; array && array[i].needs_freeing; i++) {
-                sd_device_unref(array[i].sd_device);
-                free(array[i].netdev_kind);
-                free(array[i].ssid);
-                free(array[i].qdisc);
-                strv_free(array[i].alternative_names);
-        }
-
-        return mfree(array);
-}
-DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_array_free);
-
-static int decode_netdev(sd_netlink_message *m, LinkInfo *info) {
-        int r;
-
-        assert(m);
-        assert(info);
-
-        r = sd_netlink_message_enter_container(m, IFLA_LINKINFO);
-        if (r < 0)
-                return r;
-
-        r = sd_netlink_message_read_string_strdup(m, IFLA_INFO_KIND, &info->netdev_kind);
-        if (r < 0) {
-                (void) sd_netlink_message_exit_container(m);
-                return r;
-        }
-
-        r = sd_netlink_message_enter_container(m, IFLA_INFO_DATA);
-        if (r < 0)
-                return r;
-
-        if (streq(info->netdev_kind, "bridge")) {
-                (void) sd_netlink_message_read_u32(m, IFLA_BR_FORWARD_DELAY, &info->forward_delay);
-                (void) sd_netlink_message_read_u32(m, IFLA_BR_HELLO_TIME, &info->hello_time);
-                (void) sd_netlink_message_read_u32(m, IFLA_BR_MAX_AGE, &info->max_age);
-                (void) sd_netlink_message_read_u32(m, IFLA_BR_AGEING_TIME, &info->ageing_time);
-                (void) sd_netlink_message_read_u32(m, IFLA_BR_STP_STATE, &info->stp_state);
-                (void) sd_netlink_message_read_u32(m, IFLA_BRPORT_COST, &info->cost);
-                (void) sd_netlink_message_read_u16(m, IFLA_BR_PRIORITY, &info->priority);
-                (void) sd_netlink_message_read_u8(m, IFLA_BR_MCAST_IGMP_VERSION, &info->mcast_igmp_version);
-                (void) sd_netlink_message_read_u8(m, IFLA_BRPORT_STATE, &info->port_state);
-                if (sd_netlink_message_read_u32(m, IFLA_BR_FDB_MAX_LEARNED, &info->fdb_max_learned) >= 0 &&
-                    sd_netlink_message_read_u32(m, IFLA_BR_FDB_N_LEARNED, &info->fdb_n_learned) >= 0)
-                        info->has_fdb_learned = true;
-        } if (streq(info->netdev_kind, "bond")) {
-                (void) sd_netlink_message_read_u8(m, IFLA_BOND_MODE, &info->mode);
-                (void) sd_netlink_message_read_u32(m, IFLA_BOND_MIIMON, &info->miimon);
-                (void) sd_netlink_message_read_u32(m, IFLA_BOND_DOWNDELAY, &info->downdelay);
-                (void) sd_netlink_message_read_u32(m, IFLA_BOND_UPDELAY, &info->updelay);
-        } else if (streq(info->netdev_kind, "vxlan")) {
-                (void) sd_netlink_message_read_u32(m, IFLA_VXLAN_ID, &info->vxlan_info.vni);
-
-                r = sd_netlink_message_read_in_addr(m, IFLA_VXLAN_GROUP, &info->vxlan_info.group.in);
-                if (r >= 0)
-                        info->vxlan_info.group_family = AF_INET;
-                else {
-                        r = sd_netlink_message_read_in6_addr(m, IFLA_VXLAN_GROUP6, &info->vxlan_info.group.in6);
-                        if (r >= 0)
-                                info->vxlan_info.group_family = AF_INET6;
-                }
-
-                r = sd_netlink_message_read_in_addr(m, IFLA_VXLAN_LOCAL, &info->vxlan_info.local.in);
-                if (r >= 0)
-                        info->vxlan_info.local_family = AF_INET;
-                else {
-                        r = sd_netlink_message_read_in6_addr(m, IFLA_VXLAN_LOCAL6, &info->vxlan_info.local.in6);
-                        if (r >= 0)
-                                info->vxlan_info.local_family = AF_INET6;
-                }
-
-                (void) sd_netlink_message_read_u32(m, IFLA_VXLAN_LINK, &info->vxlan_info.link);
-                (void) sd_netlink_message_read_u16(m, IFLA_VXLAN_PORT, &info->vxlan_info.dest_port);
-                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_PROXY, &info->vxlan_info.proxy);
-                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_LEARNING, &info->vxlan_info.learning);
-                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_RSC, &info->vxlan_info.rsc);
-                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_L3MISS, &info->vxlan_info.l3miss);
-                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_L2MISS, &info->vxlan_info.l2miss);
-                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_TOS, &info->vxlan_info.tos);
-                (void) sd_netlink_message_read_u8(m, IFLA_VXLAN_TTL, &info->vxlan_info.ttl);
-        } else if (streq(info->netdev_kind, "vlan"))
-                (void) sd_netlink_message_read_u16(m, IFLA_VLAN_ID, &info->vlan_id);
-        else if (STR_IN_SET(info->netdev_kind, "ipip", "sit")) {
-                (void) sd_netlink_message_read_in_addr(m, IFLA_IPTUN_LOCAL, &info->local.in);
-                (void) sd_netlink_message_read_in_addr(m, IFLA_IPTUN_REMOTE, &info->remote.in);
-        } else if (streq(info->netdev_kind, "geneve")) {
-                (void) sd_netlink_message_read_u32(m, IFLA_GENEVE_ID, &info->vni);
-
-                r = sd_netlink_message_read_in_addr(m, IFLA_GENEVE_REMOTE, &info->remote.in);
-                if (r >= 0)
-                        info->has_tunnel_ipv4 = true;
-                else
-                        (void) sd_netlink_message_read_in6_addr(m, IFLA_GENEVE_REMOTE6, &info->remote.in6);
-
-                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_TTL, &info->ttl);
-                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_TTL_INHERIT, &info->inherit);
-                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_TOS, &info->tos);
-                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_DF, &info->df);
-                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_UDP_CSUM, &info->csum);
-                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_TX, &info->csum6_tx);
-                (void) sd_netlink_message_read_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, &info->csum6_rx);
-                (void) sd_netlink_message_read_u16(m, IFLA_GENEVE_PORT, &info->tunnel_port);
-                (void) sd_netlink_message_read_u32(m, IFLA_GENEVE_LABEL, &info->label);
-        } else if (STR_IN_SET(info->netdev_kind, "gre", "gretap", "erspan")) {
-                (void) sd_netlink_message_read_in_addr(m, IFLA_GRE_LOCAL, &info->local.in);
-                (void) sd_netlink_message_read_in_addr(m, IFLA_GRE_REMOTE, &info->remote.in);
-        } else if (STR_IN_SET(info->netdev_kind, "ip6gre", "ip6gretap", "ip6erspan")) {
-                (void) sd_netlink_message_read_in6_addr(m, IFLA_GRE_LOCAL, &info->local.in6);
-                (void) sd_netlink_message_read_in6_addr(m, IFLA_GRE_REMOTE, &info->remote.in6);
-        } else if (streq(info->netdev_kind, "vti")) {
-                (void) sd_netlink_message_read_in_addr(m, IFLA_VTI_LOCAL, &info->local.in);
-                (void) sd_netlink_message_read_in_addr(m, IFLA_VTI_REMOTE, &info->remote.in);
-        } else if (streq(info->netdev_kind, "vti6")) {
-                (void) sd_netlink_message_read_in6_addr(m, IFLA_VTI_LOCAL, &info->local.in6);
-                (void) sd_netlink_message_read_in6_addr(m, IFLA_VTI_REMOTE, &info->remote.in6);
-        } else if (STR_IN_SET(info->netdev_kind, "macvlan", "macvtap"))
-                (void) sd_netlink_message_read_u32(m, IFLA_MACVLAN_MODE, &info->macvlan_mode);
-        else if (streq(info->netdev_kind, "ipvlan")) {
-                (void) sd_netlink_message_read_u16(m, IFLA_IPVLAN_MODE, &info->ipvlan_mode);
-                (void) sd_netlink_message_read_u16(m, IFLA_IPVLAN_FLAGS, &info->ipvlan_flags);
-        }
-
-        (void) sd_netlink_message_exit_container(m);
-        (void) sd_netlink_message_exit_container(m);
-
-        return 0;
-}
-
-static int decode_link(
-                sd_netlink_message *m,
-                LinkInfo *info,
-                char * const *patterns,
-                bool matched_patterns[]) {
-
-        _cleanup_strv_free_ char **altnames = NULL;
-        const char *name, *qdisc;
-        int ifindex, r;
-        uint16_t type;
-
-        assert(m);
-        assert(info);
-
-        r = sd_netlink_message_get_type(m, &type);
-        if (r < 0)
-                return r;
-
-        if (type != RTM_NEWLINK)
-                return 0;
-
-        r = sd_rtnl_message_link_get_ifindex(m, &ifindex);
-        if (r < 0)
-                return r;
-
-        r = sd_netlink_message_read_string(m, IFLA_IFNAME, &name);
-        if (r < 0)
-                return r;
-
-        r = sd_netlink_message_read_strv(m, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &altnames);
-        if (r < 0 && r != -ENODATA)
-                return r;
-
-        if (patterns) {
-                char str[DECIMAL_STR_MAX(int)];
-                size_t pos;
-
-                assert(matched_patterns);
-
-                xsprintf(str, "%i", ifindex);
-                if (!strv_fnmatch_full(patterns, str, 0, &pos) &&
-                    !strv_fnmatch_full(patterns, name, 0, &pos)) {
-                        bool match = false;
-
-                        STRV_FOREACH(p, altnames)
-                                if (strv_fnmatch_full(patterns, *p, 0, &pos)) {
-                                        match = true;
-                                        break;
-                                }
-                        if (!match)
-                                return 0;
-                }
-
-                matched_patterns[pos] = true;
-        }
-
-        r = sd_rtnl_message_link_get_type(m, &info->iftype);
-        if (r < 0)
-                return r;
-
-        strscpy(info->name, sizeof info->name, name);
-        info->ifindex = ifindex;
-        info->alternative_names = TAKE_PTR(altnames);
-
-        info->has_hw_address =
-                netlink_message_read_hw_addr(m, IFLA_ADDRESS, &info->hw_address) >= 0 &&
-                info->hw_address.length > 0;
-
-        info->has_permanent_hw_address =
-                (netlink_message_read_hw_addr(m, IFLA_PERM_ADDRESS, &info->permanent_hw_address) >= 0 ||
-                 ethtool_get_permanent_hw_addr(NULL, info->name, &info->permanent_hw_address) >= 0) &&
-                !hw_addr_is_null(&info->permanent_hw_address) &&
-                !hw_addr_equal(&info->permanent_hw_address, &info->hw_address);
-
-        (void) sd_netlink_message_read_u32(m, IFLA_MTU, &info->mtu);
-        (void) sd_netlink_message_read_u32(m, IFLA_MIN_MTU, &info->min_mtu);
-        (void) sd_netlink_message_read_u32(m, IFLA_MAX_MTU, &info->max_mtu);
-
-        info->has_rx_queues =
-                sd_netlink_message_read_u32(m, IFLA_NUM_RX_QUEUES, &info->rx_queues) >= 0 &&
-                info->rx_queues > 0;
-
-        info->has_tx_queues =
-                sd_netlink_message_read_u32(m, IFLA_NUM_TX_QUEUES, &info->tx_queues) >= 0 &&
-                info->tx_queues > 0;
-
-        if (sd_netlink_message_read(m, IFLA_STATS64, sizeof info->stats64, &info->stats64) >= 0)
-                info->has_stats64 = true;
-        else if (sd_netlink_message_read(m, IFLA_STATS, sizeof info->stats, &info->stats) >= 0)
-                info->has_stats = true;
-
-        r = sd_netlink_message_read_string(m, IFLA_QDISC, &qdisc);
-        if (r >= 0) {
-                info->qdisc = strdup(qdisc);
-                if (!info->qdisc)
-                        return log_oom();
-        }
-
-        (void) sd_netlink_message_read_u32(m, IFLA_MASTER, &info->master);
-
-        r = sd_netlink_message_enter_container(m, IFLA_AF_SPEC);
-        if (r >= 0) {
-                r = sd_netlink_message_enter_container(m, AF_INET6);
-                if (r >= 0) {
-                        r = sd_netlink_message_read_u8(m, IFLA_INET6_ADDR_GEN_MODE, &info->addr_gen_mode);
-                        if (r >= 0 && IN_SET(info->addr_gen_mode,
-                                             IN6_ADDR_GEN_MODE_EUI64,
-                                             IN6_ADDR_GEN_MODE_NONE,
-                                             IN6_ADDR_GEN_MODE_STABLE_PRIVACY,
-                                             IN6_ADDR_GEN_MODE_RANDOM))
-                                info->has_ipv6_address_generation_mode = true;
-
-                        (void) sd_netlink_message_exit_container(m);
-                }
-                (void) sd_netlink_message_exit_container(m);
-        }
-
-        /* fill kind info */
-        (void) decode_netdev(m, info);
-
-        return 1;
-}
-
-static int link_get_property(
-                sd_bus *bus,
-                const LinkInfo *link,
-                sd_bus_error *error,
-                sd_bus_message **reply,
-                const char *iface,
-                const char *propname,
-                const char *type) {
-
-        _cleanup_free_ char *path = NULL;
-        char ifindex_str[DECIMAL_STR_MAX(int)];
-        int r;
-
-        assert(bus);
-        assert(link);
-        assert(link->ifindex >= 0);
-        assert(error);
-        assert(reply);
-        assert(iface);
-        assert(propname);
-        assert(type);
-
-        xsprintf(ifindex_str, "%i", link->ifindex);
-
-        r = sd_bus_path_encode("/org/freedesktop/network1/link", ifindex_str, &path);
-        if (r < 0)
-                return r;
-
-        return sd_bus_get_property(bus, "org.freedesktop.network1", path, iface, propname, error, reply, type);
-}
-
-static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) {
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        int r;
-
-        assert(bus);
-        assert(link);
-
-        r = link_get_property(bus, link, &error, &reply, "org.freedesktop.network1.Link", "BitRates", "(tt)");
-        if (r < 0) {
-                bool quiet = sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY,
-                                                            BUS_ERROR_SPEED_METER_INACTIVE);
-
-                return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING,
-                                      r, "Failed to query link bit rates: %s", bus_error_message(&error, r));
-        }
-
-        r = sd_bus_message_read(reply, "(tt)", &link->tx_bitrate, &link->rx_bitrate);
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        r = sd_bus_message_exit_container(reply);
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        link->has_bitrates = link->tx_bitrate != UINT64_MAX && link->rx_bitrate != UINT64_MAX;
-
-        return 0;
-}
-
-static void acquire_ether_link_info(int *fd, LinkInfo *link) {
-        assert(fd);
-        assert(link);
-
-        if (ethtool_get_link_info(fd,
-                                  link->name,
-                                  &link->autonegotiation,
-                                  &link->speed,
-                                  &link->duplex,
-                                  &link->port) >= 0)
-                link->has_ethtool_link_info = true;
-}
-
-static void acquire_wlan_link_info(LinkInfo *link) {
-        _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL;
-        int r, k = 0;
-
-        assert(link);
-
-        if (!link->sd_device)
-                return;
-
-        if (!device_is_devtype(link->sd_device, "wlan"))
-                return;
-
-        r = sd_genl_socket_open(&genl);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to open generic netlink socket: %m");
-                return;
-        }
-
-        (void) sd_netlink_increase_rxbuf(genl, RCVBUF_SIZE);
-
-        r = wifi_get_interface(genl, link->ifindex, &link->wlan_iftype, &link->ssid);
-        if (r < 0)
-                log_debug_errno(r, "%s: failed to query ssid: %m", link->name);
-
-        if (link->wlan_iftype == NL80211_IFTYPE_STATION) {
-                k = wifi_get_station(genl, link->ifindex, &link->bssid);
-                if (k < 0)
-                        log_debug_errno(k, "%s: failed to query bssid: %m", link->name);
-        }
-
-        link->has_wlan_link_info = r > 0 || k > 0;
-}
-
-static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret) {
-        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
-        _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
-        _cleanup_free_ bool *matched_patterns = NULL;
-        _cleanup_close_ int fd = -EBADF;
-        size_t c = 0;
-        int r;
-
-        assert(rtnl);
-        assert(ret);
-
-        r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
-        if (r < 0)
-                return rtnl_log_create_error(r);
-
-        r = sd_netlink_message_set_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");
-
-        if (patterns) {
-                matched_patterns = new0(bool, strv_length(patterns));
-                if (!matched_patterns)
-                        return log_oom();
-        }
-
-        for (sd_netlink_message *i = reply; i; i = sd_netlink_message_next(i)) {
-                if (!GREEDY_REALLOC0(links, c + 2)) /* We keep one trailing one as marker */
-                        return -ENOMEM;
-
-                r = decode_link(i, links + c, patterns, matched_patterns);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        continue;
-
-                links[c].needs_freeing = true;
-
-                (void) sd_device_new_from_ifindex(&links[c].sd_device, links[c].ifindex);
-
-                acquire_ether_link_info(&fd, &links[c]);
-                acquire_wlan_link_info(&links[c]);
-
-                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]);
-        }
-
-        typesafe_qsort(links, c, link_info_compare);
-
-        if (bus)
-                FOREACH_ARRAY(link, links, c)
-                        (void) acquire_link_bitrates(bus, link);
-
-        *ret = TAKE_PTR(links);
-
-        if (patterns && c == 0)
-                log_warning("No interfaces matched.");
-
-        return (int) c;
-}
-
-static int list_links(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
-        _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
-        _cleanup_(table_unrefp) Table *table = NULL;
-        TableCell *cell;
-        int c, r;
-
-        r = acquire_bus(&bus);
-        if (r < 0)
-                return r;
-
-        if (arg_json_format_flags != SD_JSON_FORMAT_OFF) {
-                if (arg_all || argc <= 1)
-                        return dump_manager_description(bus);
-                else
-                        return dump_link_description(bus, strv_skip(argv, 1));
-        }
-
-        r = sd_netlink_open(&rtnl);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to netlink: %m");
-
-        c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links);
-        if (c < 0)
-                return c;
-
-        pager_open(arg_pager_flags);
-
-        table = table_new("idx", "link", "type", "operational", "setup");
-        if (!table)
-                return log_oom();
-
-        if (arg_full)
-                table_set_width(table, 0);
-
-        table_set_header(table, arg_legend);
-        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
-
-        assert_se(cell = table_get_cell(table, 0, 0));
-        (void) table_set_minimum_width(table, cell, 3);
-        (void) table_set_weight(table, cell, 0);
-        (void) table_set_ellipsize_percent(table, cell, 100);
-        (void) table_set_align_percent(table, cell, 100);
-
-        assert_se(cell = table_get_cell(table, 0, 1));
-        (void) table_set_ellipsize_percent(table, cell, 100);
-
-        FOREACH_ARRAY(link, links, c) {
-                _cleanup_free_ char *setup_state = NULL, *operational_state = NULL;
-                _cleanup_free_ char *t = NULL;
-                const char *on_color_operational, *on_color_setup;
-
-                (void) sd_network_link_get_operational_state(link->ifindex, &operational_state);
-                operational_state_to_color(link->name, operational_state, &on_color_operational, NULL);
-
-                (void) sd_network_link_get_setup_state(link->ifindex, &setup_state);
-                setup_state_to_color(setup_state, &on_color_setup, NULL);
-
-                r = net_get_type_string(link->sd_device, link->iftype, &t);
-                if (r == -ENOMEM)
-                        return log_oom();
-
-                r = table_add_many(table,
-                                   TABLE_INT, link->ifindex,
-                                   TABLE_STRING, link->name,
-                                   TABLE_STRING, t,
-                                   TABLE_STRING, operational_state,
-                                   TABLE_SET_COLOR, on_color_operational,
-                                   TABLE_STRING, setup_state ?: "unmanaged",
-                                   TABLE_SET_COLOR, on_color_setup);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        r = table_print(table, NULL);
-        if (r < 0)
-                return table_log_print_error(r);
-
-        if (arg_legend)
-                printf("\n%i links listed.\n", c);
-
-        return 0;
-}
-
-/* IEEE Organizationally Unique Identifier vendor string */
-static int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret) {
-        _cleanup_free_ char *desc = NULL;
-        const char *description;
-        char modalias[STRLEN("OUI:XXYYXXYYXXYY") + 1];
-        int r;
-
-        assert(ret);
-
-        if (!hwdb || !mac)
-                return -EINVAL;
-
-        /* skip commonly misused 00:00:00 (Xerox) prefix */
-        if (memcmp(mac, "\0\0\0", 3) == 0)
-                return -EINVAL;
-
-        xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR, ETHER_ADDR_FORMAT_VAL(*mac));
-
-        r = sd_hwdb_get(hwdb, modalias, "ID_OUI_FROM_DATABASE", &description);
-        if (r < 0)
-                return r;
-
-        desc = strdup(description);
-        if (!desc)
-                return -ENOMEM;
-
-        *ret = TAKE_PTR(desc);
-
-        return 0;
-}
-
-static int get_gateway_description(
-                sd_netlink *rtnl,
-                sd_hwdb *hwdb,
-                int ifindex,
-                int family,
-                union in_addr_union *gateway,
-                char **ret) {
-
-        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
-        int r;
-
-        assert(rtnl);
-        assert(ifindex >= 0);
-        assert(IN_SET(family, AF_INET, AF_INET6));
-        assert(gateway);
-        assert(ret);
-
-        r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_GETNEIGH, ifindex, family);
-        if (r < 0)
-                return r;
-
-        r = sd_netlink_message_set_request_dump(req, true);
-        if (r < 0)
-                return r;
-
-        r = sd_netlink_call(rtnl, req, 0, &reply);
-        if (r < 0)
-                return r;
-
-        for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
-                union in_addr_union gw = IN_ADDR_NULL;
-                struct ether_addr mac = ETHER_ADDR_NULL;
-                uint16_t type;
-                int ifi, fam;
-
-                r = sd_netlink_message_get_errno(m);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to get netlink message, ignoring: %m");
-                        continue;
-                }
-
-                r = sd_netlink_message_get_type(m, &type);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to get netlink message type, ignoring: %m");
-                        continue;
-                }
-
-                if (type != RTM_NEWNEIGH) {
-                        log_error("Got unexpected netlink message type %u, ignoring.", type);
-                        continue;
-                }
-
-                r = sd_rtnl_message_neigh_get_family(m, &fam);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to get rtnl family, ignoring: %m");
-                        continue;
-                }
-
-                if (fam != family) {
-                        log_error("Got invalid rtnl family %d, ignoring.", fam);
-                        continue;
-                }
-
-                r = sd_rtnl_message_neigh_get_ifindex(m, &ifi);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to get rtnl ifindex, ignoring: %m");
-                        continue;
-                }
-
-                if (ifindex > 0 && ifi != ifindex)
-                        continue;
-
-                switch (fam) {
-
-                case AF_INET:
-                        r = sd_netlink_message_read_in_addr(m, NDA_DST, &gw.in);
-                        if (r < 0)
-                                continue;
-
-                        break;
-
-                case AF_INET6:
-                        r = sd_netlink_message_read_in6_addr(m, NDA_DST, &gw.in6);
-                        if (r < 0)
-                                continue;
-
-                        break;
-
-                default:
-                        assert_not_reached();
-                }
-
-                if (!in_addr_equal(fam, &gw, gateway))
-                        continue;
-
-                r = sd_netlink_message_read(m, NDA_LLADDR, sizeof(mac), &mac);
-                if (r < 0)
-                        continue;
-
-                r = ieee_oui(hwdb, &mac, ret);
-                if (r < 0)
-                        continue;
-
-                return 0;
-        }
-
-        return -ENODATA;
-}
-
-static int dump_list(Table *table, const char *key, char * const *l) {
-        int r;
-
-        assert(table);
-        assert(key);
-
-        if (strv_isempty(l))
-                return 0;
-
-        r = table_add_many(table,
-                           TABLE_FIELD, key,
-                           TABLE_STRV, l);
-        if (r < 0)
-                return table_log_add_error(r);
-
-        return 0;
-}
-
-static int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex) {
-        _cleanup_free_ struct local_address *local_addrs = NULL;
-        _cleanup_strv_free_ char **buf = NULL;
-        int r, n;
-
-        assert(rtnl);
-        assert(table);
-
-        n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local_addrs);
-        if (n <= 0)
-                return n;
-
-        FOREACH_ARRAY(local, local_addrs, n) {
-                _cleanup_free_ char *description = NULL;
-
-                r = get_gateway_description(rtnl, hwdb, local->ifindex, local->family, &local->address, &description);
-                if (r < 0)
-                        log_debug_errno(r, "Could not get description of gateway, ignoring: %m");
-
-                /* Show interface name for the entry if we show entries for all interfaces */
-                r = strv_extendf(&buf, "%s%s%s%s%s%s",
-                                 IN_ADDR_TO_STRING(local->family, &local->address),
-                                 description ? " (" : "",
-                                 strempty(description),
-                                 description ? ")" : "",
-                                 ifindex <= 0 ? " on " : "",
-                                 ifindex <= 0 ? FORMAT_IFNAME_FULL(local->ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
-                if (r < 0)
-                        return log_oom();
-        }
-
-        return dump_list(table, "Gateway", buf);
-}
-
-static int dump_addresses(
-                sd_netlink *rtnl,
-                sd_dhcp_lease *lease,
-                Table *table,
-                int ifindex) {
-
-        _cleanup_free_ struct local_address *local_addrs = NULL;
-        _cleanup_strv_free_ char **buf = NULL;
-        struct in_addr dhcp4_address = {};
-        int r, n;
-
-        assert(rtnl);
-        assert(table);
-
-        n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local_addrs);
-        if (n <= 0)
-                return n;
-
-        if (lease)
-                (void) sd_dhcp_lease_get_address(lease, &dhcp4_address);
-
-        FOREACH_ARRAY(local, local_addrs, n) {
-                struct in_addr server_address;
-                bool dhcp4 = false;
-
-                if (local->family == AF_INET && in4_addr_equal(&local->address.in, &dhcp4_address))
-                        dhcp4 = sd_dhcp_lease_get_server_identifier(lease, &server_address) >= 0;
-
-                r = strv_extendf(&buf, "%s%s%s%s%s%s",
-                                 IN_ADDR_TO_STRING(local->family, &local->address),
-                                 dhcp4 ? " (DHCPv4 via " : "",
-                                 dhcp4 ? IN4_ADDR_TO_STRING(&server_address) : "",
-                                 dhcp4 ? ")" : "",
-                                 ifindex <= 0 ? " on " : "",
-                                 ifindex <= 0 ? FORMAT_IFNAME_FULL(local->ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
-                if (r < 0)
-                        return log_oom();
-        }
-
-        return dump_list(table, "Address", buf);
-}
-
-static int dump_address_labels(sd_netlink *rtnl) {
-        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
-        _cleanup_(table_unrefp) Table *table = NULL;
-        TableCell *cell;
-        int r;
-
-        assert(rtnl);
-
-        r = sd_rtnl_message_new_addrlabel(rtnl, &req, RTM_GETADDRLABEL, 0, AF_INET6);
-        if (r < 0)
-                return log_error_errno(r, "Could not allocate RTM_GETADDRLABEL message: %m");
-
-        r = sd_netlink_message_set_request_dump(req, true);
-        if (r < 0)
-                return r;
-
-        r = sd_netlink_call(rtnl, req, 0, &reply);
-        if (r < 0)
-                return r;
-
-        table = table_new("label", "prefix/prefixlen");
-        if (!table)
-                return log_oom();
-
-        if (arg_full)
-                table_set_width(table, 0);
-
-        r = table_set_sort(table, (size_t) 0);
-        if (r < 0)
-                return r;
-
-        assert_se(cell = table_get_cell(table, 0, 0));
-        (void) table_set_align_percent(table, cell, 100);
-        (void) table_set_ellipsize_percent(table, cell, 100);
-
-        assert_se(cell = table_get_cell(table, 0, 1));
-        (void) table_set_align_percent(table, cell, 100);
-
-        for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
-                struct in6_addr prefix;
-                uint8_t prefixlen;
-                uint32_t label;
-
-                r = sd_netlink_message_get_errno(m);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to get netlink message, ignoring: %m");
-                        continue;
-                }
-
-                r = sd_netlink_message_read_u32(m, IFAL_LABEL, &label);
-                if (r < 0 && r != -ENODATA) {
-                        log_error_errno(r, "Could not read IFAL_LABEL, ignoring: %m");
-                        continue;
-                }
-
-                r = sd_netlink_message_read_in6_addr(m, IFAL_ADDRESS, &prefix);
-                if (r < 0)
-                        continue;
-
-                r = sd_rtnl_message_addrlabel_get_prefixlen(m, &prefixlen);
-                if (r < 0)
-                        continue;
-
-                r = table_add_cell(table, NULL, TABLE_UINT32, &label);
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                r = table_add_cell_stringf(table, NULL, "%s/%u", IN6_ADDR_TO_STRING(&prefix), prefixlen);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        r = table_print(table, NULL);
-        if (r < 0)
-                return table_log_print_error(r);
-
-        return 0;
-}
-
-static int list_address_labels(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
-        int r;
-
-        r = sd_netlink_open(&rtnl);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to netlink: %m");
-
-        return dump_address_labels(rtnl);
-}
-
-typedef struct InterfaceInfo {
-        int ifindex;
-        const char *ifname;
-        char **altnames;
-        sd_json_variant *v;
-} InterfaceInfo;
-
-static void interface_info_done(InterfaceInfo *p) {
-        if (!p)
-                return;
-
-        strv_free(p->altnames);
-        sd_json_variant_unref(p->v);
-}
-
-static const sd_json_dispatch_field interface_info_dispatch_table[] = {
-        { "InterfaceIndex",            _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int,          offsetof(InterfaceInfo, ifindex),  SD_JSON_MANDATORY },
-        { "InterfaceName",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(InterfaceInfo, ifname),   SD_JSON_MANDATORY },
-        { "InterfaceAlternativeNames", SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,         offsetof(InterfaceInfo, altnames), 0                 },
-        { "Neighbors",                 SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_variant,      offsetof(InterfaceInfo, v),        0                 },
-        {},
-};
-
-typedef struct LLDPNeighborInfo {
-        const char *chassis_id;
-        const char *port_id;
-        const char *port_description;
-        const char *system_name;
-        const char *system_description;
-        uint16_t capabilities;
-} LLDPNeighborInfo;
-
-static const sd_json_dispatch_field lldp_neighbor_dispatch_table[] = {
-        { "ChassisID",           SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, chassis_id),         0 },
-        { "PortID",              SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_id),            0 },
-        { "PortDescription",     SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_description),   0 },
-        { "SystemName",          SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_name),        0 },
-        { "SystemDescription",   SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_description), 0 },
-        { "EnabledCapabilities", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16,       offsetof(LLDPNeighborInfo, capabilities),       0 },
-        {},
-};
-
-static int dump_lldp_neighbors(sd_varlink *vl, Table *table, int ifindex) {
-        _cleanup_strv_free_ char **buf = NULL;
-        sd_json_variant *reply;
-        int r;
-
-        assert(vl);
-        assert(table);
-        assert(ifindex > 0);
-
-        r = varlink_callbo_and_log(
-                        vl,
-                        "io.systemd.Network.GetLLDPNeighbors",
-                        &reply,
-                        SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", ifindex));
-        if (r < 0)
-                return r;
-
-        sd_json_variant *i;
-        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(reply, "Neighbors")) {
-                _cleanup_(interface_info_done) InterfaceInfo info = {};
-
-                r = sd_json_dispatch(i, interface_info_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &info);
-                if (r < 0)
-                        return r;
-
-                if (info.ifindex != ifindex)
-                        continue;
-
-                sd_json_variant *neighbor;
-                JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) {
-                        LLDPNeighborInfo neighbor_info = {};
-
-                        r = sd_json_dispatch(neighbor, lldp_neighbor_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &neighbor_info);
-                        if (r < 0)
-                                return r;
-
-                        r = strv_extendf(&buf, "%s%s%s%s on port %s%s%s%s",
-                                         strna(neighbor_info.system_name),
-                                         isempty(neighbor_info.system_description) ? "" : " (",
-                                         strempty(neighbor_info.system_description),
-                                         isempty(neighbor_info.system_description) ? "" : ")",
-                                         strna(neighbor_info.port_id),
-                                         isempty(neighbor_info.port_description) ? "" : " (",
-                                         strempty(neighbor_info.port_description),
-                                         isempty(neighbor_info.port_description) ? "" : ")");
-                        if (r < 0)
-                                return log_oom();
-                }
-        }
-
-        return dump_list(table, "Connected To", buf);
-}
-
-static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const LinkInfo *link) {
-        _cleanup_strv_free_ char **buf = NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        int r;
-
-        assert(table);
-        assert(prefix);
-        assert(bus);
-        assert(link);
-
-        r = link_get_property(bus, link, &error, &reply, "org.freedesktop.network1.DHCPServer", "Leases", "a(uayayayayt)");
-        if (r < 0) {
-                bool quiet = sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY);
-
-                log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING,
-                               r, "Failed to query link DHCP leases: %s", bus_error_message(&error, r));
-                return 0;
-        }
-
-        r = sd_bus_message_enter_container(reply, 'a', "(uayayayayt)");
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        while ((r = sd_bus_message_enter_container(reply, 'r', "uayayayayt")) > 0) {
-                _cleanup_free_ char *id = NULL, *ip = NULL;
-                const void *client_id, *addr, *gtw, *hwaddr;
-                size_t client_id_sz, sz;
-                uint64_t expiration;
-                uint32_t family;
-
-                r = sd_bus_message_read(reply, "u", &family);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-
-                r = sd_bus_message_read_array(reply, 'y', &client_id, &client_id_sz);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-
-                r = sd_bus_message_read_array(reply, 'y', &addr, &sz);
-                if (r < 0 || sz != 4)
-                        return bus_log_parse_error(r);
-
-                r = sd_bus_message_read_array(reply, 'y', &gtw, &sz);
-                if (r < 0 || sz != 4)
-                        return bus_log_parse_error(r);
-
-                r = sd_bus_message_read_array(reply, 'y', &hwaddr, &sz);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-
-                r = sd_bus_message_read_basic(reply, 't', &expiration);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-
-                r = sd_dhcp_client_id_to_string_from_raw(client_id, client_id_sz, &id);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-
-                r = in_addr_to_string(family, addr, &ip);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-
-                r = strv_extendf(&buf, "%s (to %s)", ip, id);
-                if (r < 0)
-                        return log_oom();
-
-                r = sd_bus_message_exit_container(reply);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-        }
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        r = sd_bus_message_exit_container(reply);
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        r = sd_bus_message_exit_container(reply);
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        if (strv_isempty(buf)) {
-                r = strv_extendf(&buf, "none");
-                if (r < 0)
-                        return log_oom();
-        }
-
-        return dump_list(table, prefix, buf);
-}
-
-static int dump_ifindexes(Table *table, const char *prefix, const int *ifindexes) {
-        int r;
-
-        assert(table);
-        assert(prefix);
-
-        if (!ifindexes)
-                return 0;
-
-        for (unsigned c = 0; ifindexes[c] > 0; c++) {
-                if (c == 0)
-                        r = table_add_cell(table, NULL, TABLE_FIELD, prefix);
-                else
-                        r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                r = table_add_cell(table, NULL, TABLE_IFINDEX, &ifindexes[c]);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        return 0;
-}
-
-#define DUMP_STATS_ONE(name, val_name)                                          \
-        ({                                                                      \
-                r = table_add_cell(table, NULL, TABLE_FIELD, name);             \
-                if (r < 0)                                                      \
-                        return table_log_add_error(r);                          \
-                r = table_add_cell(table, NULL,                                 \
-                                   info->has_stats64 ? TABLE_UINT64 : TABLE_UINT32, \
-                                   info->has_stats64 ? (void*) &info->stats64.val_name : (void*) &info->stats.val_name); \
-                if (r < 0)                                                      \
-                        return table_log_add_error(r);                          \
-        })
-
-static int dump_statistics(Table *table, const LinkInfo *info) {
-        int r;
-
-        assert(table);
-        assert(info);
-
-        if (!arg_stats)
-                return 0;
-
-        if (!info->has_stats64 && !info->has_stats)
-                return 0;
-
-        DUMP_STATS_ONE("Rx Packets", rx_packets);
-        DUMP_STATS_ONE("Tx Packets", tx_packets);
-        DUMP_STATS_ONE("Rx Bytes", rx_bytes);
-        DUMP_STATS_ONE("Tx Bytes", tx_bytes);
-        DUMP_STATS_ONE("Rx Errors", rx_errors);
-        DUMP_STATS_ONE("Tx Errors", tx_errors);
-        DUMP_STATS_ONE("Rx Dropped", rx_dropped);
-        DUMP_STATS_ONE("Tx Dropped", tx_dropped);
-        DUMP_STATS_ONE("Multicast Packets", multicast);
-        DUMP_STATS_ONE("Collisions", collisions);
-
-        return 0;
-}
-
-static int dump_hw_address(Table *table, sd_hwdb *hwdb, const char *field, const struct hw_addr_data *addr) {
-        _cleanup_free_ char *description = NULL;
-        int r;
-
-        assert(table);
-        assert(field);
-        assert(addr);
-
-        if (addr->length == ETH_ALEN)
-                (void) ieee_oui(hwdb, &addr->ether, &description);
-
-        r = table_add_cell(table, NULL, TABLE_FIELD, field);
-        if (r < 0)
-                return table_log_add_error(r);
-
-        r = table_add_cell_stringf(table, NULL, "%s%s%s%s",
-                                   HW_ADDR_TO_STR(addr),
-                                   description ? " (" : "",
-                                   strempty(description),
-                                   description ? ")" : "");
-        if (r < 0)
-                return table_log_add_error(r);
-
-        return 0;
-}
-
-static OutputFlags get_output_flags(void) {
-        return
-                arg_all * OUTPUT_SHOW_ALL |
-                (arg_full || !on_tty() || pager_have()) * OUTPUT_FULL_WIDTH |
-                colors_enabled() * OUTPUT_COLOR;
-}
-
-static int show_logs(const LinkInfo *info) {
-        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
-        int r;
-
-        if (arg_lines == 0)
-                return 0;
-
-        r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE);
-        if (r < 0)
-                return log_error_errno(r, "Failed to open journal: %m");
-
-        r = add_match_this_boot(j, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add boot matches: %m");
-
-        if (info) {
-                (void) (
-                       (r = journal_add_matchf(j, "_KERNEL_DEVICE=n%i", info->ifindex)) || /* kernel */
-                       (r = sd_journal_add_disjunction(j)) ||
-                       (r = journal_add_match_pair(j, "INTERFACE", info->name)) || /* networkd */
-                       (r = sd_journal_add_disjunction(j)) ||
-                       (r = journal_add_match_pair(j, "DEVICE", info->name)) /* udevd */
-                );
-                if (r < 0)
-                        return log_error_errno(r, "Failed to add link matches: %m");
-        } else {
-                r = add_matches_for_unit(j, "systemd-networkd.service");
-                if (r < 0)
-                        return log_error_errno(r, "Failed to add unit matches: %m");
-
-                r = add_matches_for_unit(j, "systemd-networkd-wait-online.service");
-                if (r < 0)
-                        return log_error_errno(r, "Failed to add unit matches: %m");
-        }
-
-        return show_journal(
-                        stdout,
-                        j,
-                        OUTPUT_SHORT,
-                        0,
-                        0,
-                        arg_lines,
-                        get_output_flags() | OUTPUT_BEGIN_NEWLINE,
-                        NULL);
-}
-
-static int table_add_string_line(Table *table, const char *key, const char *value) {
-        int r;
-
-        assert(table);
-        assert(key);
-
-        if (isempty(value))
-                return 0;
-
-        r = table_add_many(table,
-                           TABLE_FIELD, key,
-                           TABLE_STRING, value);
-        if (r < 0)
-                return table_log_add_error(r);
-
-        return 0;
-}
-
-static int format_dropins(char **dropins) {
-        STRV_FOREACH(d, dropins) {
-                _cleanup_free_ char *s = NULL;
-                int glyph = *(d + 1) == NULL ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH;
-
-                s = strjoin(special_glyph(glyph), *d);
-                if (!s)
-                        return log_oom();
-
-                free_and_replace(*d, s);
-        }
-
-        return 0;
-}
-
-static int link_status_one(
-                sd_bus *bus,
-                sd_netlink *rtnl,
-                sd_hwdb *hwdb,
-                sd_varlink *vl,
-                const LinkInfo *info) {
-
-        _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **sip = NULL, **search_domains = NULL,
-                **route_domains = NULL, **link_dropins = NULL, **network_dropins = NULL;
-        _cleanup_free_ char *t = NULL, *network = NULL, *iaid = NULL, *duid = NULL, *captive_portal = NULL,
-                *setup_state = NULL, *operational_state = NULL, *online_state = NULL, *activation_policy = NULL;
-        const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL,
-                *on_color_operational, *off_color_operational, *on_color_setup, *off_color_setup, *on_color_online;
-        _cleanup_free_ int *carrier_bound_to = NULL, *carrier_bound_by = NULL;
-        _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
-        _cleanup_(table_unrefp) Table *table = NULL;
-        int r;
-
-        assert(bus);
-        assert(rtnl);
-        assert(vl);
-        assert(info);
-
-        (void) sd_network_link_get_operational_state(info->ifindex, &operational_state);
-        operational_state_to_color(info->name, operational_state, &on_color_operational, &off_color_operational);
-
-        (void) sd_network_link_get_online_state(info->ifindex, &online_state);
-        online_state_to_color(online_state, &on_color_online, NULL);
-
-        (void) sd_network_link_get_setup_state(info->ifindex, &setup_state);
-        setup_state_to_color(setup_state, &on_color_setup, &off_color_setup);
-
-        (void) sd_network_link_get_dns(info->ifindex, &dns);
-        (void) sd_network_link_get_search_domains(info->ifindex, &search_domains);
-        (void) sd_network_link_get_route_domains(info->ifindex, &route_domains);
-        (void) sd_network_link_get_ntp(info->ifindex, &ntp);
-        (void) sd_network_link_get_sip(info->ifindex, &sip);
-        (void) sd_network_link_get_captive_portal(info->ifindex, &captive_portal);
-        (void) sd_network_link_get_network_file(info->ifindex, &network);
-        (void) sd_network_link_get_network_file_dropins(info->ifindex, &network_dropins);
-        (void) sd_network_link_get_carrier_bound_to(info->ifindex, &carrier_bound_to);
-        (void) sd_network_link_get_carrier_bound_by(info->ifindex, &carrier_bound_by);
-        (void) sd_network_link_get_activation_policy(info->ifindex, &activation_policy);
-
-        if (info->sd_device) {
-                const char *joined;
-
-                (void) sd_device_get_property_value(info->sd_device, "ID_NET_LINK_FILE", &link);
-
-                if (sd_device_get_property_value(info->sd_device, "ID_NET_LINK_FILE_DROPINS", &joined) >= 0) {
-                        r = strv_split_full(&link_dropins, joined, ":", EXTRACT_CUNESCAPE);
-                        if (r < 0)
-                                return r;
-                }
-
-                (void) sd_device_get_property_value(info->sd_device, "ID_NET_DRIVER", &driver);
-                (void) sd_device_get_property_value(info->sd_device, "ID_PATH", &path);
-                (void) device_get_vendor_string(info->sd_device, &vendor);
-                (void) device_get_model_string(info->sd_device, &model);
-        }
-
-        r = net_get_type_string(info->sd_device, info->iftype, &t);
-        if (r == -ENOMEM)
-                return log_oom();
-
-        char lease_file[STRLEN("/run/systemd/netif/leases/") + DECIMAL_STR_MAX(int)];
-        xsprintf(lease_file, "/run/systemd/netif/leases/%i", info->ifindex);
-
-        (void) dhcp_lease_load(&lease, lease_file);
-
-        r = format_dropins(network_dropins);
-        if (r < 0)
-                return r;
-
-        if (strv_prepend(&network_dropins, network) < 0)
-                return log_oom();
-
-        r = format_dropins(link_dropins);
-        if (r < 0)
-                return r;
-
-        if (strv_prepend(&link_dropins, link) < 0)
-                return log_oom();
-
-        table = table_new_vertical();
-        if (!table)
-                return log_oom();
-
-        if (arg_full)
-                table_set_width(table, 0);
-
-        /* unit files and basic states. */
-        r = table_add_many(table,
-                           TABLE_FIELD, "Link File",
-                           TABLE_STRV, link_dropins ?: STRV_MAKE("n/a"),
-                           TABLE_FIELD, "Network File",
-                           TABLE_STRV, network_dropins ?: STRV_MAKE("n/a"),
-                           TABLE_FIELD, "State");
-        if (r < 0)
-                return table_log_add_error(r);
-
-        r = table_add_cell_stringf(table, NULL, "%s%s%s (%s%s%s)",
-                                   on_color_operational, strna(operational_state), off_color_operational,
-                                   on_color_setup, setup_state ?: "unmanaged", off_color_setup);
-        if (r < 0)
-                return table_log_add_error(r);
-
-        r = table_add_many(table,
-                           TABLE_FIELD, "Online state",
-                           TABLE_STRING, online_state ?: "unknown",
-                           TABLE_SET_COLOR, on_color_online);
-        if (r < 0)
-                return table_log_add_error(r);
-
-        r = table_add_string_line(table, "Type", t);
-        if (r < 0)
-                return r;
-
-        r = table_add_string_line(table, "Kind", info->netdev_kind);
-        if (r < 0)
-                return r;
-
-        r = table_add_string_line(table, "Path", path);
-        if (r < 0)
-                return r;
-
-        r = table_add_string_line(table, "Driver", driver);
-        if (r < 0)
-                return r;
-
-        r = table_add_string_line(table, "Vendor", vendor);
-        if (r < 0)
-                return r;
-
-        r = table_add_string_line(table, "Model", model);
-        if (r < 0)
-                return r;
-
-        strv_sort(info->alternative_names);
-        r = dump_list(table, "Alternative Names", info->alternative_names);
-        if (r < 0)
-                return r;
-
-        if (info->has_hw_address) {
-                r = dump_hw_address(table, hwdb, "Hardware Address", &info->hw_address);
-                if (r < 0)
-                        return r;
-        }
-
-        if (info->has_permanent_hw_address) {
-                r = dump_hw_address(table, hwdb, "Permanent Hardware Address", &info->permanent_hw_address);
-                if (r < 0)
-                        return r;
-        }
-
-        if (info->mtu > 0) {
-                char min_str[DECIMAL_STR_MAX(uint32_t)], max_str[DECIMAL_STR_MAX(uint32_t)];
-
-                xsprintf(min_str, "%" PRIu32, info->min_mtu);
-                xsprintf(max_str, "%" PRIu32, info->max_mtu);
-
-                r = table_add_cell(table, NULL, TABLE_FIELD, "MTU");
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                r = table_add_cell_stringf(table, NULL, "%" PRIu32 "%s%s%s%s%s%s%s",
-                                           info->mtu,
-                                           info->min_mtu > 0 || info->max_mtu > 0 ? " (" : "",
-                                           info->min_mtu > 0 ? "min: " : "",
-                                           info->min_mtu > 0 ? min_str : "",
-                                           info->min_mtu > 0 && info->max_mtu > 0 ? ", " : "",
-                                           info->max_mtu > 0 ? "max: " : "",
-                                           info->max_mtu > 0 ? max_str : "",
-                                           info->min_mtu > 0 || info->max_mtu > 0 ? ")" : "");
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        r = table_add_string_line(table, "QDisc", info->qdisc);
-        if (r < 0)
-                return r;
-
-        if (info->master > 0) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "Master",
-                                   TABLE_IFINDEX, info->master);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        if (info->has_ipv6_address_generation_mode) {
-                static const struct {
-                        const char *mode;
-                } mode_table[] = {
-                        { "eui64" },
-                        { "none" },
-                        { "stable-privacy" },
-                        { "random" },
-                };
-
-                r = table_add_many(table,
-                                   TABLE_FIELD, "IPv6 Address Generation Mode",
-                                   TABLE_STRING, mode_table[info->addr_gen_mode]);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        if (streq_ptr(info->netdev_kind, "bridge")) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "Forward Delay",
-                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->forward_delay),
-                                   TABLE_FIELD, "Hello Time",
-                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->hello_time),
-                                   TABLE_FIELD, "Max Age",
-                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->max_age),
-                                   TABLE_FIELD, "Ageing Time",
-                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->ageing_time),
-                                   TABLE_FIELD, "Priority",
-                                   TABLE_UINT16, info->priority,
-                                   TABLE_FIELD, "STP",
-                                   TABLE_BOOLEAN, info->stp_state > 0,
-                                   TABLE_FIELD, "Multicast IGMP Version",
-                                   TABLE_UINT8, info->mcast_igmp_version,
-                                   TABLE_FIELD, "Cost",
-                                   TABLE_UINT32, info->cost);
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                if (info->has_fdb_learned) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "FDB Learned",
-                                           TABLE_UINT32, info->fdb_n_learned,
-                                           TABLE_FIELD, "FDB Max Learned",
-                                           TABLE_UINT32, info->fdb_max_learned);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (info->port_state <= BR_STATE_BLOCKING) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Port State",
-                                           TABLE_STRING, bridge_state_to_string(info->port_state));
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-        } else if (streq_ptr(info->netdev_kind, "bond")) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "Mode",
-                                   TABLE_STRING, bond_mode_to_string(info->mode),
-                                   TABLE_FIELD, "Miimon",
-                                   TABLE_TIMESPAN_MSEC, info->miimon * USEC_PER_MSEC,
-                                   TABLE_FIELD, "Updelay",
-                                   TABLE_TIMESPAN_MSEC, info->updelay * USEC_PER_MSEC,
-                                   TABLE_FIELD, "Downdelay",
-                                   TABLE_TIMESPAN_MSEC, info->downdelay * USEC_PER_MSEC);
-                if (r < 0)
-                        return table_log_add_error(r);
-
-        } else if (streq_ptr(info->netdev_kind, "vxlan")) {
-                char ttl[CONST_MAX(STRLEN("auto") + 1, DECIMAL_STR_MAX(uint8_t))];
-
-                if (info->vxlan_info.vni > 0) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "VNI",
-                                           TABLE_UINT32, info->vxlan_info.vni);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (IN_SET(info->vxlan_info.group_family, AF_INET, AF_INET6)) {
-                        const char *p;
-
-                        r = in_addr_is_multicast(info->vxlan_info.group_family, &info->vxlan_info.group);
-                        if (r <= 0)
-                                p = "Remote";
-                        else
-                                p = "Group";
-
-                        r = table_add_many(table,
-                                           TABLE_FIELD, p,
-                                           info->vxlan_info.group_family == AF_INET ? TABLE_IN_ADDR : TABLE_IN6_ADDR, &info->vxlan_info.group);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (IN_SET(info->vxlan_info.local_family, AF_INET, AF_INET6)) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Local",
-                                           info->vxlan_info.local_family == AF_INET ? TABLE_IN_ADDR : TABLE_IN6_ADDR, &info->vxlan_info.local);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (info->vxlan_info.dest_port > 0) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Destination Port",
-                                           TABLE_UINT16, be16toh(info->vxlan_info.dest_port));
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (info->vxlan_info.link > 0) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Underlying Device",
-                                           TABLE_IFINDEX, info->vxlan_info.link);
-                        if (r < 0)
-                                 return table_log_add_error(r);
-                }
-
-                r = table_add_many(table,
-                                   TABLE_FIELD, "Learning",
-                                   TABLE_BOOLEAN, info->vxlan_info.learning,
-                                   TABLE_FIELD, "RSC",
-                                   TABLE_BOOLEAN, info->vxlan_info.rsc,
-                                   TABLE_FIELD, "L3MISS",
-                                   TABLE_BOOLEAN, info->vxlan_info.l3miss,
-                                   TABLE_FIELD, "L2MISS",
-                                   TABLE_BOOLEAN, info->vxlan_info.l2miss);
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                if (info->vxlan_info.tos > 1) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "TOS",
-                                           TABLE_UINT8, info->vxlan_info.tos);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (info->vxlan_info.ttl > 0)
-                        xsprintf(ttl, "%" PRIu8, info->vxlan_info.ttl);
-                else
-                        strcpy(ttl, "auto");
-
-                r = table_add_many(table,
-                                   TABLE_FIELD, "TTL",
-                                   TABLE_STRING, ttl);
-                if (r < 0)
-                        return table_log_add_error(r);
-
-        } else if (streq_ptr(info->netdev_kind, "vlan") && info->vlan_id > 0) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "VLan Id",
-                                   TABLE_UINT16, info->vlan_id);
-                if (r < 0)
-                        return table_log_add_error(r);
-
-        } else if (STRPTR_IN_SET(info->netdev_kind, "ipip", "sit", "gre", "gretap", "erspan", "vti")) {
-                if (in_addr_is_set(AF_INET, &info->local)) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Local",
-                                           TABLE_IN_ADDR, &info->local);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (in_addr_is_set(AF_INET, &info->remote)) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Remote",
-                                           TABLE_IN_ADDR, &info->remote);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-        } else if (STRPTR_IN_SET(info->netdev_kind, "ip6gre", "ip6gretap", "ip6erspan", "vti6")) {
-                if (in_addr_is_set(AF_INET6, &info->local)) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Local",
-                                           TABLE_IN6_ADDR, &info->local);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (in_addr_is_set(AF_INET6, &info->remote)) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Remote",
-                                           TABLE_IN6_ADDR, &info->remote);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-        } else if (streq_ptr(info->netdev_kind, "geneve")) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "VNI",
-                                   TABLE_UINT32, info->vni);
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                if (info->has_tunnel_ipv4 && in_addr_is_set(AF_INET, &info->remote)) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Remote",
-                                           TABLE_IN_ADDR, &info->remote);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                } else if (in_addr_is_set(AF_INET6, &info->remote)) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Remote",
-                                           TABLE_IN6_ADDR, &info->remote);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (info->ttl > 0) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "TTL",
-                                           TABLE_UINT8, info->ttl);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (info->tos > 0) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "TOS",
-                                           TABLE_UINT8, info->tos);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                r = table_add_many(table,
-                                   TABLE_FIELD, "Port",
-                                   TABLE_UINT16, info->tunnel_port,
-                                   TABLE_FIELD, "Inherit",
-                                   TABLE_STRING, geneve_df_to_string(info->inherit));
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                if (info->df > 0) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "IPDoNotFragment",
-                                           TABLE_UINT8, info->df);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                r = table_add_many(table,
-                                   TABLE_FIELD, "UDPChecksum",
-                                   TABLE_BOOLEAN, info->csum,
-                                   TABLE_FIELD, "UDP6ZeroChecksumTx",
-                                   TABLE_BOOLEAN, info->csum6_tx,
-                                   TABLE_FIELD, "UDP6ZeroChecksumRx",
-                                   TABLE_BOOLEAN, info->csum6_rx);
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                if (info->label > 0) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "FlowLabel",
-                                           TABLE_UINT32, info->label);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-        } else if (STRPTR_IN_SET(info->netdev_kind, "macvlan", "macvtap")) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "Mode",
-                                   TABLE_STRING, macvlan_mode_to_string(info->macvlan_mode));
-                if (r < 0)
-                        return table_log_add_error(r);
-
-        } else if (streq_ptr(info->netdev_kind, "ipvlan")) {
-                const char *p;
-
-                if (info->ipvlan_flags & IPVLAN_F_PRIVATE)
-                        p = "private";
-                else if (info->ipvlan_flags & IPVLAN_F_VEPA)
-                        p = "vepa";
-                else
-                        p = "bridge";
-
-                r = table_add_cell(table, NULL, TABLE_FIELD, "Mode");
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                r = table_add_cell_stringf(table, NULL, "%s (%s)",
-                                           ipvlan_mode_to_string(info->ipvlan_mode), p);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        if (info->has_wlan_link_info) {
-                _cleanup_free_ char *esc = NULL;
-
-                r = table_add_cell(table, NULL, TABLE_FIELD, "Wi-Fi access point");
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                if (info->ssid)
-                        esc = cescape(info->ssid);
-
-                r = table_add_cell_stringf(table, NULL, "%s (%s)",
-                                           strnull(esc),
-                                           ETHER_ADDR_TO_STR(&info->bssid));
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        if (info->has_bitrates) {
-                r = table_add_cell(table, NULL, TABLE_FIELD, "Bit Rate (Tx/Rx)");
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                r = table_add_cell_stringf(table, NULL, "%sbps/%sbps",
-                                           FORMAT_BYTES_FULL(info->tx_bitrate, 0),
-                                           FORMAT_BYTES_FULL(info->rx_bitrate, 0));
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        if (info->has_tx_queues || info->has_rx_queues) {
-                r = table_add_cell(table, NULL, TABLE_FIELD, "Number of Queues (Tx/Rx)");
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                r = table_add_cell_stringf(table, NULL, "%" PRIu32 "/%" PRIu32, info->tx_queues, info->rx_queues);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        if (info->has_ethtool_link_info) {
-                if (IN_SET(info->autonegotiation, AUTONEG_DISABLE, AUTONEG_ENABLE)) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Auto negotiation",
-                                           TABLE_BOOLEAN, info->autonegotiation == AUTONEG_ENABLE);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                if (info->speed > 0 && info->speed != UINT64_MAX) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Speed",
-                                           TABLE_BPS, info->speed);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                r = table_add_string_line(table, "Duplex", duplex_to_string(info->duplex));
-                if (r < 0)
-                        return r;
-
-                r = table_add_string_line(table, "Port", port_to_string(info->port));
-                if (r < 0)
-                        return r;
-        }
-
-        r = dump_addresses(rtnl, lease, table, info->ifindex);
-        if (r < 0)
-                return r;
-
-        r = dump_gateways(rtnl, hwdb, table, info->ifindex);
-        if (r < 0)
-                return r;
-
-        r = dump_list(table, "DNS", dns);
-        if (r < 0)
-                return r;
-
-        r = dump_list(table, "Search Domains", search_domains);
-        if (r < 0)
-                return r;
-
-        r = dump_list(table, "Route Domains", route_domains);
-        if (r < 0)
-                return r;
-
-        r = dump_list(table, "NTP", ntp);
-        if (r < 0)
-                return r;
-
-        r = dump_list(table, "SIP", sip);
-        if (r < 0)
-                return r;
-
-        r = dump_ifindexes(table, "Carrier Bound To", carrier_bound_to);
-        if (r < 0)
-                return r;
-
-        r = dump_ifindexes(table, "Carrier Bound By", carrier_bound_by);
-        if (r < 0)
-                return r;
-
-        r = table_add_string_line(table, "Activation Policy", activation_policy);
-        if (r < 0)
-                return r;
-
-        r = sd_network_link_get_required_for_online(info->ifindex);
-        if (r >= 0) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "Required For Online",
-                                   TABLE_BOOLEAN, r);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        if (captive_portal) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "Captive Portal",
-                                   TABLE_STRING, captive_portal,
-                                   TABLE_SET_URL, captive_portal);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        if (lease) {
-                const sd_dhcp_client_id *client_id;
-                const char *tz;
-
-                r = sd_dhcp_lease_get_timezone(lease, &tz);
-                if (r >= 0) {
-                        r = table_add_many(table,
-                                           TABLE_FIELD, "Time Zone",
-                                           TABLE_STRING, tz);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-
-                r = sd_dhcp_lease_get_client_id(lease, &client_id);
-                if (r >= 0) {
-                        _cleanup_free_ char *id = NULL;
-
-                        r = sd_dhcp_client_id_to_string(client_id, &id);
-                        if (r >= 0) {
-                                r = table_add_many(table,
-                                                   TABLE_FIELD, "DHCPv4 Client ID",
-                                                   TABLE_STRING, id);
-                                if (r < 0)
-                                        return table_log_add_error(r);
-                        }
-                }
-        }
-
-        r = sd_network_link_get_dhcp6_client_iaid_string(info->ifindex, &iaid);
-        if (r >= 0) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "DHCPv6 Client IAID",
-                                   TABLE_STRING, iaid);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        r = sd_network_link_get_dhcp6_client_duid_string(info->ifindex, &duid);
-        if (r >= 0) {
-                r = table_add_many(table,
-                                   TABLE_FIELD, "DHCPv6 Client DUID",
-                                   TABLE_STRING, duid);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        r = dump_lldp_neighbors(vl, table, info->ifindex);
-        if (r < 0)
-                return r;
-
-        r = dump_dhcp_leases(table, "Offered DHCP leases", bus, info);
-        if (r < 0)
-                return r;
-
-        r = dump_statistics(table, info);
-        if (r < 0)
-                return r;
-
-        /* First line: circle, ifindex, ifname. */
-        printf("%s%s%s %d: %s\n",
-               on_color_operational, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE), off_color_operational,
-               info->ifindex, info->name);
-
-        r = table_print(table, NULL);
-        if (r < 0)
-                return table_log_print_error(r);
-
-        return show_logs(info);
-}
-
-static int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) {
-        _cleanup_free_ char *operational_state = NULL, *online_state = NULL, *netifs_joined = NULL;
-        _cleanup_strv_free_ char **netifs = NULL, **dns = NULL, **ntp = NULL, **search_domains = NULL, **route_domains = NULL;
-        const char *on_color_operational, *off_color_operational, *on_color_online;
-        _cleanup_(table_unrefp) Table *table = NULL;
-        int r;
-
-        assert(rtnl);
-
-        (void) sd_network_get_operational_state(&operational_state);
-        operational_state_to_color(NULL, operational_state, &on_color_operational, &off_color_operational);
-
-        (void) sd_network_get_online_state(&online_state);
-        online_state_to_color(online_state, &on_color_online, NULL);
-
-        table = table_new_vertical();
-        if (!table)
-                return log_oom();
-
-        if (arg_full)
-                table_set_width(table, 0);
-
-        r = get_files_in_directory("/run/systemd/netif/links/", &netifs);
-        if (r < 0 && r != -ENOENT)
-                return log_error_errno(r, "Failed to list network interfaces: %m");
-        else if (r > 0) {
-                netifs_joined = strv_join(netifs, ", ");
-                if (!netifs_joined)
-                        return log_oom();
-        }
-
-        r = table_add_many(table,
-                           TABLE_FIELD, "State",
-                           TABLE_STRING, strna(operational_state),
-                           TABLE_SET_COLOR, on_color_operational,
-                           TABLE_FIELD, "Online state",
-                           TABLE_STRING, online_state ?: "unknown",
-                           TABLE_SET_COLOR, on_color_online);
-        if (r < 0)
-                return table_log_add_error(r);
-
-        r = dump_addresses(rtnl, NULL, table, 0);
-        if (r < 0)
-                return r;
-
-        r = dump_gateways(rtnl, hwdb, table, 0);
-        if (r < 0)
-                return r;
-
-        (void) sd_network_get_dns(&dns);
-        r = dump_list(table, "DNS", dns);
-        if (r < 0)
-                return r;
-
-        (void) sd_network_get_search_domains(&search_domains);
-        r = dump_list(table, "Search Domains", search_domains);
-        if (r < 0)
-                return r;
-
-        (void) sd_network_get_route_domains(&route_domains);
-        r = dump_list(table, "Route Domains", route_domains);
-        if (r < 0)
-                return r;
-
-        (void) sd_network_get_ntp(&ntp);
-        r = dump_list(table, "NTP", ntp);
-        if (r < 0)
-                return r;
-
-        printf("%s%s%s Interfaces: %s\n",
-               on_color_operational, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE), off_color_operational,
-               strna(netifs_joined));
-
-        r = table_print(table, NULL);
-        if (r < 0)
-                return table_log_print_error(r);
-
-        return show_logs(NULL);
-}
-
-static int link_status(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
-        _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
-        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
-        _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
-        int r, c;
-
-        r = acquire_bus(&bus);
-        if (r < 0)
-                return r;
-
-        if (arg_json_format_flags != SD_JSON_FORMAT_OFF) {
-                if (arg_all || argc <= 1)
-                        return dump_manager_description(bus);
-                else
-                        return dump_link_description(bus, strv_skip(argv, 1));
-        }
-
-        pager_open(arg_pager_flags);
-
-        r = sd_netlink_open(&rtnl);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to netlink: %m");
-
-        r = sd_hwdb_new(&hwdb);
-        if (r < 0)
-                log_debug_errno(r, "Failed to open hardware database: %m");
-
-        r = varlink_connect_networkd(&vl);
-        if (r < 0)
-                return r;
-
-        if (arg_all)
-                c = acquire_link_info(bus, rtnl, NULL, &links);
-        else if (argc <= 1)
-                return system_status(rtnl, hwdb);
-        else
-                c = acquire_link_info(bus, rtnl, argv + 1, &links);
-        if (c < 0)
-                return c;
-
-        r = 0;
-
-        bool first = true;
-        FOREACH_ARRAY(i, links, c) {
-                if (!first)
-                        putchar('\n');
-
-                RET_GATHER(r, link_status_one(bus, rtnl, hwdb, vl, i));
-
-                first = false;
-        }
-
-        return r;
-}
-
-static char *lldp_capabilities_to_string(uint64_t x) {
-        static const char characters[] = {
-                'o', 'p', 'b', 'w', 'r', 't', 'd', 'a', 'c', 's', 'm',
-        };
-        char *ret;
-        unsigned i;
-
-        ret = new(char, ELEMENTSOF(characters) + 1);
-        if (!ret)
-                return NULL;
-
-        for (i = 0; i < ELEMENTSOF(characters); i++)
-                ret[i] = (x & (1U << i)) ? characters[i] : '.';
-
-        ret[i] = 0;
-        return ret;
-}
-
-static void lldp_capabilities_legend(uint16_t x) {
-        unsigned cols = columns();
-        static const char* const table[] = {
-                "o - Other",
-                "p - Repeater",
-                "b - Bridge",
-                "w - WLAN Access Point",
-                "r - Router",
-                "t - Telephone",
-                "d - DOCSIS cable device",
-                "a - Station",
-                "c - Customer VLAN",
-                "s - Service VLAN",
-                "m - Two-port MAC Relay (TPMR)",
-        };
-
-        if (x == 0)
-                return;
-
-        printf("\nCapability Flags:\n");
-        for (unsigned w = 0, i = 0; i < ELEMENTSOF(table); i++)
-                if (x & (1U << i) || arg_all) {
-                        bool newline;
-
-                        newline = w + strlen(table[i]) + (w == 0 ? 0 : 2) > cols;
-                        if (newline)
-                                w = 0;
-                        w += printf("%s%s%s", newline ? "\n" : "", w == 0 ? "" : "; ", table[i]);
-                }
-        puts("");
-}
-
-static bool interface_match_pattern(const InterfaceInfo *info, char * const *patterns) {
-        assert(info);
-
-        if (strv_isempty(patterns))
-                return true;
-
-        if (strv_fnmatch(patterns, info->ifname))
-                return true;
-
-        char str[DECIMAL_STR_MAX(int)];
-        xsprintf(str, "%i", info->ifindex);
-        if (strv_fnmatch(patterns, str))
-                return true;
-
-        STRV_FOREACH(a, info->altnames)
-                if (strv_fnmatch(patterns, *a))
-                        return true;
-
-        return false;
-}
-
-static int dump_lldp_neighbors_json(sd_json_variant *reply, char * const *patterns) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *v = NULL;
-        int r;
-
-        assert(reply);
-
-        if (strv_isempty(patterns))
-                return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL);
-
-        /* Filter and dump the result. */
-
-        sd_json_variant *i;
-        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(reply, "Neighbors")) {
-                _cleanup_(interface_info_done) InterfaceInfo info = {};
-
-                r = sd_json_dispatch(i, interface_info_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &info);
-                if (r < 0)
-                        return r;
-
-                if (!interface_match_pattern(&info, patterns))
-                        continue;
-
-                r = sd_json_variant_append_array(&array, i);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to append json variant to array: %m");
-        }
-
-        r = sd_json_buildo(
-                        &v,
-                        SD_JSON_BUILD_PAIR_CONDITION(sd_json_variant_is_blank_array(array), "Neighbors", SD_JSON_BUILD_EMPTY_ARRAY),
-                        SD_JSON_BUILD_PAIR_CONDITION(!sd_json_variant_is_blank_array(array), "Neighbors", SD_JSON_BUILD_VARIANT(array)));
-        if (r < 0)
-                return log_error_errno(r, "Failed to build json varinat: %m");
-
-        return sd_json_variant_dump(v, arg_json_format_flags, NULL, NULL);
-}
-
-static int link_lldp_status(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
-        _cleanup_(table_unrefp) Table *table = NULL;
-        sd_json_variant *reply;
-        uint64_t all = 0;
-        TableCell *cell;
-        size_t m = 0;
-        int r;
-
-        r = varlink_connect_networkd(&vl);
-        if (r < 0)
-                return r;
-
-        r = varlink_call_and_log(vl, "io.systemd.Network.GetLLDPNeighbors", NULL, &reply);
-        if (r < 0)
-                return r;
-
-        if (arg_json_format_flags != SD_JSON_FORMAT_OFF)
-                return dump_lldp_neighbors_json(reply, strv_skip(argv, 1));
-
-        pager_open(arg_pager_flags);
-
-        table = table_new("index",
-                          "link",
-                          "system-name",
-                          "system-description",
-                          "chassis-id",
-                          "port-id",
-                          "port-description",
-                          "caps");
-        if (!table)
-                return log_oom();
-
-        if (arg_full)
-                table_set_width(table, 0);
-
-        table_set_header(table, arg_legend);
-        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
-        table_set_sort(table, (size_t) 0, (size_t) 2);
-        table_hide_column_from_display(table, (size_t) 0);
-
-        /* Make the capabilities not truncated */
-        assert_se(cell = table_get_cell(table, 0, 7));
-        table_set_minimum_width(table, cell, 11);
-
-        sd_json_variant *i;
-        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(reply, "Neighbors")) {
-                _cleanup_(interface_info_done) InterfaceInfo info = {};
-
-                r = sd_json_dispatch(i, interface_info_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &info);
-                if (r < 0)
-                        return r;
-
-                if (!interface_match_pattern(&info, strv_skip(argv, 1)))
-                        continue;
-
-                sd_json_variant *neighbor;
-                JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) {
-                        LLDPNeighborInfo neighbor_info = {};
-
-                        r = sd_json_dispatch(neighbor, lldp_neighbor_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &neighbor_info);
-                        if (r < 0)
-                                return r;
-
-                        all |= neighbor_info.capabilities;
-
-                        _cleanup_free_ char *cap_str = lldp_capabilities_to_string(neighbor_info.capabilities);
-
-                        r = table_add_many(table,
-                                           TABLE_INT,    info.ifindex,
-                                           TABLE_STRING, info.ifname,
-                                           TABLE_STRING, neighbor_info.system_name,
-                                           TABLE_STRING, neighbor_info.system_description,
-                                           TABLE_STRING, neighbor_info.chassis_id,
-                                           TABLE_STRING, neighbor_info.port_id,
-                                           TABLE_STRING, neighbor_info.port_description,
-                                           TABLE_STRING, cap_str);
-                        if (r < 0)
-                                return table_log_add_error(r);
-
-                        m++;
-                }
-        }
-
-        r = table_print(table, NULL);
-        if (r < 0)
-                return table_log_print_error(r);
-
-        if (arg_legend) {
-                lldp_capabilities_legend(all);
-                printf("\n%zu neighbor(s) listed.\n", m);
-        }
-
-        return 0;
-}
-
-static int link_delete_send_message(sd_netlink *rtnl, int index) {
-        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
-        int r;
-
-        assert(rtnl);
-        assert(index >= 0);
-
-        r = sd_rtnl_message_new_link(rtnl, &req, RTM_DELLINK, index);
-        if (r < 0)
-                return rtnl_log_create_error(r);
-
-        r = sd_netlink_call(rtnl, req, 0, NULL);
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
-static int link_up_down_send_message(sd_netlink *rtnl, char *command, int index) {
-        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
-        int r;
-
-        assert(rtnl);
-        assert(index >= 0);
-
-        r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, index);
-        if (r < 0)
-                return rtnl_log_create_error(r);
-
-        if (streq(command, "up"))
-                r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP);
-        else
-                r = sd_rtnl_message_link_set_flags(req, 0, IFF_UP);
-        if (r < 0)
-                return log_error_errno(r, "Could not set link flags: %m");
-
-        r = sd_netlink_call(rtnl, req, 0, NULL);
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
-static int link_up_down(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
-        _cleanup_set_free_ Set *indexes = NULL;
-        int index, r;
-        void *p;
-
-        r = sd_netlink_open(&rtnl);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to netlink: %m");
-
-        indexes = set_new(NULL);
-        if (!indexes)
-                return log_oom();
-
-        for (int i = 1; i < argc; i++) {
-                index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
-                if (index < 0)
-                        return index;
-
-                r = set_put(indexes, INT_TO_PTR(index));
-                if (r < 0)
-                        return log_oom();
-        }
-
-        SET_FOREACH(p, indexes) {
-                index = PTR_TO_INT(p);
-                r = link_up_down_send_message(rtnl, argv[0], index);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to bring %s interface %s: %m",
-                                               argv[0], FORMAT_IFNAME_FULL(index, FORMAT_IFNAME_IFINDEX));
-        }
-
-        return r;
-}
-
-static int link_delete(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
-        _cleanup_set_free_ Set *indexes = NULL;
-        int index, r;
-        void *p;
-
-        r = sd_netlink_open(&rtnl);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to netlink: %m");
-
-        indexes = set_new(NULL);
-        if (!indexes)
-                return log_oom();
-
-        for (int i = 1; i < argc; i++) {
-                index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
-                if (index < 0)
-                        return index;
-
-                r = set_put(indexes, INT_TO_PTR(index));
-                if (r < 0)
-                        return log_oom();
-        }
-
-        SET_FOREACH(p, indexes) {
-                index = PTR_TO_INT(p);
-                r = link_delete_send_message(rtnl, index);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to delete interface %s: %m",
-                                               FORMAT_IFNAME_FULL(index, FORMAT_IFNAME_IFINDEX));
-        }
-
-        return r;
-}
-
-static int link_renew_one(sd_bus *bus, int index, const char *name) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        int r;
-
-        assert(bus);
-        assert(index >= 0);
-        assert(name);
-
-        r = bus_call_method(bus, bus_network_mgr, "RenewLink", &error, NULL, "i", index);
-        if (r < 0)
-                return log_error_errno(r, "Failed to renew dynamic configuration of interface %s: %s",
-                                       name, bus_error_message(&error, r));
-
-        return 0;
-}
-
-static int link_renew(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
-        int r;
-
-        r = acquire_bus(&bus);
-        if (r < 0)
-                return r;
-
-        (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
-
-        r = 0;
-
-        for (int i = 1; i < argc; i++) {
-                int index;
-
-                index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
-                if (index < 0)
-                        return index;
-
-                RET_GATHER(r, link_renew_one(bus, index, argv[i]));
-        }
-
-        return r;
-}
-
-static int link_force_renew_one(sd_bus *bus, int index, const char *name) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        int r;
-
-        assert(bus);
-        assert(index >= 0);
-        assert(name);
-
-        r = bus_call_method(bus, bus_network_mgr, "ForceRenewLink", &error, NULL, "i", index);
-        if (r < 0)
-                return log_error_errno(r, "Failed to force renew dynamic configuration of interface %s: %s",
-                                       name, bus_error_message(&error, r));
-
-        return 0;
-}
-
-static int link_force_renew(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
-        int k = 0, r;
-
-        r = acquire_bus(&bus);
-        if (r < 0)
-                return r;
-
-        (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
-
-        for (int i = 1; i < argc; i++) {
-                int index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
-                if (index < 0)
-                        return index;
-
-                r = link_force_renew_one(bus, index, argv[i]);
-                if (r < 0 && k >= 0)
-                        k = r;
-        }
-
-        return k;
-}
-
-static int verb_reload(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        int r;
-
-        r = acquire_bus(&bus);
-        if (r < 0)
-                return r;
-
-        (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
-
-        r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to reload network settings: %s", bus_error_message(&error, r));
-
-        return 0;
-}
-
-static int verb_reconfigure(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
-        _cleanup_set_free_ Set *indexes = NULL;
-        int index, r;
-        void *p;
-
-        r = acquire_bus(&bus);
-        if (r < 0)
-                return r;
-
-        (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
-
-        indexes = set_new(NULL);
-        if (!indexes)
-                return log_oom();
-
-        for (int i = 1; i < argc; i++) {
-                index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
-                if (index < 0)
-                        return index;
-
-                r = set_put(indexes, INT_TO_PTR(index));
-                if (r < 0)
-                        return log_oom();
-        }
-
-        SET_FOREACH(p, indexes) {
-                index = PTR_TO_INT(p);
-                r = bus_call_method(bus, bus_network_mgr, "ReconfigureLink", &error, NULL, "i", index);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to reconfigure network interface %s: %s",
-                                               FORMAT_IFNAME_FULL(index, FORMAT_IFNAME_IFINDEX),
-                                               bus_error_message(&error, r));
-        }
-
-        return 0;
-}
-
-static int verb_persistent_storage(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
-        bool ready;
-        int r;
-
-        r = parse_boolean(argv[1]);
-        if (r < 0)
-                return log_error_errno(r, "Failed to parse argument: %s", argv[1]);
-        ready = r;
-
-        r = varlink_connect_networkd(&vl);
-        if (r < 0)
-                return r;
-
-        if (ready) {
-                _cleanup_close_ int fd = -EBADF;
-
-                fd = open("/var/lib/systemd/network/", O_CLOEXEC | O_DIRECTORY);
-                if (fd < 0)
-                        return log_error_errno(errno, "Failed to open /var/lib/systemd/network/: %m");
-
-                r = sd_varlink_push_fd(vl, fd);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to push file descriptor of /var/lib/systemd/network/ into varlink: %m");
-
-                TAKE_FD(fd);
-        }
-
-        return varlink_callbo_and_log(
-                        vl,
-                        "io.systemd.Network.SetPersistentStorage",
-                        /* reply= */ NULL,
-                        SD_JSON_BUILD_PAIR_BOOLEAN("Ready", ready));
-}
-
 static int help(void) {
         _cleanup_free_ char *link = NULL;
         int r;
index f88b2d9ab595e9ba0d5c76a43cd67deffcf1e005..cb76fe41e1bf5cf8f5d0260085f443bcb1031fc0 100644 (file)
@@ -3,8 +3,6 @@
 
 #include <stdbool.h>
 
-#include "sd-bus.h"
-
 #include "output-mode.h"
 #include "pager.h"
 
@@ -20,6 +18,3 @@ extern unsigned arg_lines;
 extern char *arg_drop_in;
 extern sd_json_format_flags_t arg_json_format_flags;
 extern bool arg_ask_password;
-
-bool networkd_is_running(void);
-int acquire_bus(sd_bus **ret);