]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkctl.c
Merge pull request #33386 from yuwata/journal-timestamp
[thirdparty/systemd.git] / src / network / networkctl.c
index cf9d17c8b2fe98c37a760fd3b623125f195126c9..1384a8321694dac80bf860b5a748a892468e6f5a 100644 (file)
@@ -1,9 +1,10 @@
 /* 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 <net/if.h>
 #include <stdbool.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -15,7 +16,7 @@
 #include "sd-device.h"
 #include "sd-dhcp-client.h"
 #include "sd-hwdb.h"
-#include "sd-lldp-rx.h"
+#include "sd-json.h"
 #include "sd-netlink.h"
 #include "sd-network.h"
 
@@ -38,6 +39,8 @@
 #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"
@@ -70,6 +73,7 @@
 #include "terminal-util.h"
 #include "udev-util.h"
 #include "unit-def.h"
+#include "varlink.h"
 #include "verbs.h"
 #include "wifi-util.h"
 
@@ -88,35 +92,54 @@ bool arg_full = false;
 bool arg_runtime = false;
 unsigned arg_lines = 10;
 char *arg_drop_in = NULL;
-JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
+sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
 
 STATIC_DESTRUCTOR_REGISTER(arg_drop_in, freep);
 
-static int check_netns_match(sd_bus *bus) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        struct stat st;
+static int varlink_connect_networkd(Varlink **ret_varlink) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL;
+        sd_json_variant *reply;
         uint64_t id;
         int r;
 
-        assert(bus);
+        r = 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");
 
-        r = bus_get_property_trivial(bus, bus_network_mgr, "NamespaceId", &error, 't', &id);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to query network namespace of networkd, ignoring: %s", bus_error_message(&error, r));
-                return 0;
-        }
-        if (id == 0) {
+        (void) varlink_set_description(vl, "varlink-network");
+
+        r = 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.");
-                return 0;
-        }
+        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 (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 (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;
 }
 
@@ -150,7 +173,7 @@ int acquire_bus(sd_bus **ret) {
                 return log_error_errno(r, "Failed to connect to system bus: %m");
 
         if (networkd_is_running()) {
-                r = check_netns_match(bus);
+                r = varlink_connect_networkd(/* ret_varlink = */ NULL);
                 if (r < 0)
                         return r;
         } else
@@ -160,7 +183,7 @@ int acquire_bus(sd_bus **ret) {
         return 0;
 }
 
-static int get_description(sd_bus *bus, JsonVariant **ret) {
+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;
@@ -177,7 +200,7 @@ static int get_description(sd_bus *bus, JsonVariant **ret) {
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        r = json_parse(text, 0, ret, NULL, NULL);
+        r = sd_json_parse(text, 0, ret, NULL, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to parse JSON: %m");
 
@@ -185,7 +208,7 @@ static int get_description(sd_bus *bus, JsonVariant **ret) {
 }
 
 static int dump_manager_description(sd_bus *bus) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
         int r;
 
         assert(bus);
@@ -194,14 +217,14 @@ static int dump_manager_description(sd_bus *bus) {
         if (r < 0)
                 return r;
 
-        json_variant_dump(v, arg_json_format_flags, NULL, NULL);
+        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_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
         _cleanup_free_ bool *matched_patterns = NULL;
-        JsonVariant *i;
+        sd_json_variant *i;
         size_t c = 0;
         int r;
 
@@ -216,23 +239,23 @@ static int dump_link_description(sd_bus *bus, char * const *patterns) {
         if (!matched_patterns)
                 return log_oom();
 
-        JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(v, "Interfaces")) {
+        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 = json_variant_string(json_variant_by_key(i, "Name"));
-                index = json_variant_integer(json_variant_by_key(i, "Index"));
+                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;
-                        JsonVariant *a;
+                        sd_json_variant *a;
 
-                        JSON_VARIANT_ARRAY_FOREACH(a, json_variant_by_key(i, "AlternativeNames"))
-                                if (strv_fnmatch_full(patterns, json_variant_string(a), 0, &pos)) {
+                        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;
                                 }
@@ -242,7 +265,7 @@ static int dump_link_description(sd_bus *bus, char * const *patterns) {
                 }
 
                 matched_patterns[pos] = true;
-                json_variant_dump(i, arg_json_format_flags, NULL, NULL);
+                sd_json_variant_dump(i, arg_json_format_flags, NULL, NULL);
                 c++;
         }
 
@@ -780,14 +803,14 @@ static void acquire_ether_link_info(int *fd, LinkInfo *link) {
 
 static void acquire_wlan_link_info(LinkInfo *link) {
         _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL;
-        const char *type = NULL;
         int r, k = 0;
 
         assert(link);
 
-        if (link->sd_device)
-                (void) sd_device_get_devtype(link->sd_device, &type);
-        if (!streq_ptr(type, "wlan"))
+        if (!link->sd_device)
+                return;
+
+        if (!device_is_devtype(link->sd_device, "wlan"))
                 return;
 
         r = sd_genl_socket_open(&genl);
@@ -900,7 +923,7 @@ static int list_links(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return r;
 
-        if (arg_json_format_flags != JSON_FORMAT_OFF) {
+        if (arg_json_format_flags != SD_JSON_FORMAT_OFF) {
                 if (arg_all || argc <= 1)
                         return dump_manager_description(bus);
                 else
@@ -1052,9 +1075,7 @@ static int get_gateway_description(
                 }
 
                 if (type != RTM_NEWNEIGH) {
-                        log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                        "Got unexpected netlink message type %u, ignoring",
-                                        type);
+                        log_error("Got unexpected netlink message type %u, ignoring.", type);
                         continue;
                 }
 
@@ -1065,7 +1086,7 @@ static int get_gateway_description(
                 }
 
                 if (fam != family) {
-                        log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Got invalid rtnl family %d, ignoring", fam);
+                        log_error("Got invalid rtnl family %d, ignoring.", fam);
                         continue;
                 }
 
@@ -1095,7 +1116,7 @@ static int get_gateway_description(
                         break;
 
                 default:
-                        continue;
+                        assert_not_reached();
                 }
 
                 if (!in_addr_equal(fam, &gw, gateway))
@@ -1197,7 +1218,7 @@ static int dump_addresses(
 
                 r = strv_extendf(&buf, "%s%s%s%s%s%s",
                                  IN_ADDR_TO_STRING(local->family, &local->address),
-                                 dhcp4 ? " (DHCP4 via " : "",
+                                 dhcp4 ? " (DHCPv4 via " : "",
                                  dhcp4 ? IN4_ADDR_TO_STRING(&server_address) : "",
                                  dhcp4 ? ")" : "",
                                  ifindex <= 0 ? " on " : "",
@@ -1299,96 +1320,96 @@ static int list_address_labels(int argc, char *argv[], void *userdata) {
         return dump_address_labels(rtnl);
 }
 
-static int open_lldp_neighbors(int ifindex, FILE **ret) {
-        _cleanup_fclose_ FILE *f = NULL;
-        char p[STRLEN("/run/systemd/netif/lldp/") + DECIMAL_STR_MAX(int)];
-
-        assert(ifindex >= 0);
-        assert(ret);
-
-        xsprintf(p, "/run/systemd/netif/lldp/%i", ifindex);
-
-        f = fopen(p, "re");
-        if (!f)
-                return -errno;
-
-        *ret = TAKE_PTR(f);
-        return 0;
-}
-
-static int next_lldp_neighbor(FILE *f, sd_lldp_neighbor **ret) {
-        _cleanup_free_ void *raw = NULL;
-        size_t l;
-        le64_t u;
-        int r;
-
-        assert(f);
-        assert(ret);
-
-        l = fread(&u, 1, sizeof(u), f);
-        if (l == 0 && feof(f))
-                return 0;
-        if (l != sizeof(u))
-                return -EBADMSG;
-
-        /* each LLDP packet is at most MTU size, but let's allow up to 4KiB just in case */
-        if (le64toh(u) >= 4096)
-                return -EBADMSG;
-
-        raw = new(uint8_t, le64toh(u));
-        if (!raw)
-                return -ENOMEM;
-
-        if (fread(raw, 1, le64toh(u), f) != le64toh(u))
-                return -EBADMSG;
+typedef struct InterfaceInfo {
+        int ifindex;
+        const char *ifname;
+        char **altnames;
+        sd_json_variant *v;
+} InterfaceInfo;
 
-        r = sd_lldp_neighbor_from_raw(ret, raw, le64toh(u));
-        if (r < 0)
-                return r;
+static void interface_info_done(InterfaceInfo *p) {
+        if (!p)
+                return;
 
-        return 1;
+        strv_free(p->altnames);
+        sd_json_variant_unref(p->v);
 }
 
-static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) {
+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(Varlink *vl, Table *table, int ifindex) {
         _cleanup_strv_free_ char **buf = NULL;
-        _cleanup_fclose_ FILE *f = NULL;
+        sd_json_variant *reply;
         int r;
 
+        assert(vl);
         assert(table);
-        assert(prefix);
         assert(ifindex > 0);
 
-        r = open_lldp_neighbors(ifindex, &f);
-        if (r == -ENOENT)
-                return 0;
+        r = varlink_callb_and_log(vl, "io.systemd.Network.GetLLDPNeighbors", &reply,
+                                  SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", ifindex)));
         if (r < 0)
                 return r;
 
-        for (;;) {
-                const char *system_name = NULL, *port_id = NULL, *port_description = NULL;
-                _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+        sd_json_variant *i;
+        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(reply, "Neighbors")) {
+                _cleanup_(interface_info_done) InterfaceInfo info = {};
 
-                r = next_lldp_neighbor(f, &n);
+                r = sd_json_dispatch(i, interface_info_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &info);
                 if (r < 0)
                         return r;
-                if (r == 0)
-                        break;
 
-                (void) sd_lldp_neighbor_get_system_name(n, &system_name);
-                (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id);
-                (void) sd_lldp_neighbor_get_port_description(n, &port_description);
+                if (info.ifindex != ifindex)
+                        continue;
 
-                r = strv_extendf(&buf, "%s on port %s%s%s%s",
-                                 strna(system_name),
-                                 strna(port_id),
-                                 isempty(port_description) ? "" : " (",
-                                 strempty(port_description),
-                                 isempty(port_description) ? "" : ")");
-                if (r < 0)
-                        return log_oom();
+                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, prefix, buf);
+        return dump_list(table, "Connected To", buf);
 }
 
 static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const LinkInfo *link) {
@@ -1446,7 +1467,7 @@ static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const
                 if (r < 0)
                         return bus_log_parse_error(r);
 
-                r = sd_dhcp_client_id_to_string(client_id, client_id_sz, &id);
+                r = sd_dhcp_client_id_to_string_from_raw(client_id, client_id_sz, &id);
                 if (r < 0)
                         return bus_log_parse_error(r);
 
@@ -1585,7 +1606,7 @@ static int show_logs(const LinkInfo *info) {
         if (arg_lines == 0)
                 return 0;
 
-        r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+        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");
 
@@ -1594,22 +1615,12 @@ static int show_logs(const LinkInfo *info) {
                 return log_error_errno(r, "Failed to add boot matches: %m");
 
         if (info) {
-                char m1[STRLEN("_KERNEL_DEVICE=n") + DECIMAL_STR_MAX(int)];
-                const char *m2, *m3;
-
-                /* kernel */
-                xsprintf(m1, "_KERNEL_DEVICE=n%i", info->ifindex);
-                /* networkd */
-                m2 = strjoina("INTERFACE=", info->name);
-                /* udevd */
-                m3 = strjoina("DEVICE=", info->name);
-
-                (void)(
-                       (r = sd_journal_add_match(j, m1, 0)) ||
+                (void) (
+                       (r = journal_add_matchf(j, "_KERNEL_DEVICE=n%i", info->ifindex)) || /* kernel */
                        (r = sd_journal_add_disjunction(j)) ||
-                       (r = sd_journal_add_match(j, m2, 0)) ||
+                       (r = journal_add_match_pair(j, "INTERFACE", info->name)) || /* networkd */
                        (r = sd_journal_add_disjunction(j)) ||
-                       (r = sd_journal_add_match(j, m3, 0))
+                       (r = journal_add_match_pair(j, "DEVICE", info->name)) /* udevd */
                 );
                 if (r < 0)
                         return log_error_errno(r, "Failed to add link matches: %m");
@@ -1671,6 +1682,7 @@ static int link_status_one(
                 sd_bus *bus,
                 sd_netlink *rtnl,
                 sd_hwdb *hwdb,
+                Varlink *vl,
                 const LinkInfo *info) {
 
         _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **sip = NULL, **search_domains = NULL,
@@ -1686,6 +1698,7 @@ static int link_status_one(
 
         assert(bus);
         assert(rtnl);
+        assert(vl);
         assert(info);
 
         (void) sd_network_link_get_operational_state(info->ifindex, &operational_state);
@@ -2259,8 +2272,7 @@ static int link_status_one(
         }
 
         if (lease) {
-                const void *client_id;
-                size_t client_id_len;
+                const sd_dhcp_client_id *client_id;
                 const char *tz;
 
                 r = sd_dhcp_lease_get_timezone(lease, &tz);
@@ -2272,14 +2284,14 @@ static int link_status_one(
                                 return table_log_add_error(r);
                 }
 
-                r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len);
+                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, client_id_len, &id);
+                        r = sd_dhcp_client_id_to_string(client_id, &id);
                         if (r >= 0) {
                                 r = table_add_many(table,
-                                                   TABLE_FIELD, "DHCP4 Client ID",
+                                                   TABLE_FIELD, "DHCPv4 Client ID",
                                                    TABLE_STRING, id);
                                 if (r < 0)
                                         return table_log_add_error(r);
@@ -2290,7 +2302,7 @@ static int link_status_one(
         r = sd_network_link_get_dhcp6_client_iaid_string(info->ifindex, &iaid);
         if (r >= 0) {
                 r = table_add_many(table,
-                                   TABLE_FIELD, "DHCP6 Client IAID",
+                                   TABLE_FIELD, "DHCPv6 Client IAID",
                                    TABLE_STRING, iaid);
                 if (r < 0)
                         return table_log_add_error(r);
@@ -2299,13 +2311,13 @@ static int link_status_one(
         r = sd_network_link_get_dhcp6_client_duid_string(info->ifindex, &duid);
         if (r >= 0) {
                 r = table_add_many(table,
-                                   TABLE_FIELD, "DHCP6 Client DUID",
+                                   TABLE_FIELD, "DHCPv6 Client DUID",
                                    TABLE_STRING, duid);
                 if (r < 0)
                         return table_log_add_error(r);
         }
 
-        r = dump_lldp_neighbors(table, "Connected To", info->ifindex);
+        r = dump_lldp_neighbors(vl, table, info->ifindex);
         if (r < 0)
                 return r;
 
@@ -2413,6 +2425,7 @@ 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_(varlink_flush_close_unrefp) Varlink *vl = NULL;
         _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
         int r, c;
 
@@ -2420,7 +2433,7 @@ static int link_status(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return r;
 
-        if (arg_json_format_flags != JSON_FORMAT_OFF) {
+        if (arg_json_format_flags != SD_JSON_FORMAT_OFF) {
                 if (arg_all || argc <= 1)
                         return dump_manager_description(bus);
                 else
@@ -2437,6 +2450,10 @@ static int link_status(int argc, char *argv[], void *userdata) {
         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)
@@ -2453,7 +2470,7 @@ static int link_status(int argc, char *argv[], void *userdata) {
                 if (!first)
                         putchar('\n');
 
-                RET_GATHER(r, link_status_one(bus, rtnl, hwdb, i));
+                RET_GATHER(r, link_status_one(bus, rtnl, hwdb, vl, i));
 
                 first = false;
         }
@@ -2461,7 +2478,7 @@ static int link_status(int argc, char *argv[], void *userdata) {
         return r;
 }
 
-static char *lldp_capabilities_to_string(uint16_t x) {
+static char *lldp_capabilities_to_string(uint64_t x) {
         static const char characters[] = {
                 'o', 'p', 'b', 'w', 'r', 't', 'd', 'a', 'c', 's', 'm',
         };
@@ -2511,30 +2528,94 @@ static void lldp_capabilities_legend(uint16_t x) {
         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_build(&v,
+                SD_JSON_BUILD_OBJECT(
+                        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_netlink_unrefp) sd_netlink *rtnl = NULL;
-        _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
+        _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL;
         _cleanup_(table_unrefp) Table *table = NULL;
-        int r, c, m = 0;
-        uint16_t all = 0;
+        sd_json_variant *reply;
+        uint64_t all = 0;
         TableCell *cell;
+        size_t m = 0;
+        int r;
 
-        r = sd_netlink_open(&rtnl);
+        r = varlink_connect_networkd(&vl);
         if (r < 0)
-                return log_error_errno(r, "Failed to connect to netlink: %m");
+                return r;
 
-        c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links);
-        if (c < 0)
-                return c;
+        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("link",
-                          "chassis-id",
+        table = table_new("index",
+                          "link",
                           "system-name",
-                          "caps",
+                          "system-description",
+                          "chassis-id",
                           "port-id",
-                          "port-description");
+                          "port-description",
+                          "caps");
         if (!table)
                 return log_oom();
 
@@ -2542,53 +2623,46 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) {
                 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);
 
-        assert_se(cell = table_get_cell(table, 0, 3));
+        /* Make the capabilities not truncated */
+        assert_se(cell = table_get_cell(table, 0, 7));
         table_set_minimum_width(table, cell, 11);
-        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
 
-        FOREACH_ARRAY(link, links, c) {
-                _cleanup_fclose_ FILE *f = NULL;
+        sd_json_variant *i;
+        JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(reply, "Neighbors")) {
+                _cleanup_(interface_info_done) InterfaceInfo info = {};
 
-                r = open_lldp_neighbors(link->ifindex, &f);
-                if (r == -ENOENT)
-                        continue;
-                if (r < 0) {
-                        log_warning_errno(r, "Failed to open LLDP data for %i, ignoring: %m", link->ifindex);
+                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;
-                }
 
-                for (;;) {
-                        const char *chassis_id = NULL, *port_id = NULL, *system_name = NULL, *port_description = NULL;
-                        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
-                        _cleanup_free_ char *capabilities = NULL;
-                        uint16_t cc;
+                sd_json_variant *neighbor;
+                JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) {
+                        LLDPNeighborInfo neighbor_info = {};
 
-                        r = next_lldp_neighbor(f, &n);
-                        if (r < 0) {
-                                log_warning_errno(r, "Failed to read neighbor data: %m");
-                                break;
-                        }
-                        if (r == 0)
-                                break;
+                        r = sd_json_dispatch(neighbor, lldp_neighbor_dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &neighbor_info);
+                        if (r < 0)
+                                return r;
 
-                        (void) sd_lldp_neighbor_get_chassis_id_as_string(n, &chassis_id);
-                        (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id);
-                        (void) sd_lldp_neighbor_get_system_name(n, &system_name);
-                        (void) sd_lldp_neighbor_get_port_description(n, &port_description);
+                        all |= neighbor_info.capabilities;
 
-                        if (sd_lldp_neighbor_get_enabled_capabilities(n, &cc) >= 0) {
-                                capabilities = lldp_capabilities_to_string(cc);
-                                all |= cc;
-                        }
+                        _cleanup_free_ char *cap_str = lldp_capabilities_to_string(neighbor_info.capabilities);
 
                         r = table_add_many(table,
-                                           TABLE_STRING, link->name,
-                                           TABLE_STRING, chassis_id,
-                                           TABLE_STRING, system_name,
-                                           TABLE_STRING, capabilities,
-                                           TABLE_STRING, port_id,
-                                           TABLE_STRING, port_description);
+                                           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);
 
@@ -2602,7 +2676,7 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) {
 
         if (arg_legend) {
                 lldp_capabilities_legend(all);
-                printf("\n%i neighbors listed.\n", m);
+                printf("\n%zu neighbor(s) listed.\n", m);
         }
 
         return 0;
@@ -2853,6 +2927,38 @@ static int verb_reconfigure(int argc, char *argv[], void *userdata) {
         return 0;
 }
 
+static int verb_persistent_storage(int argc, char *argv[], void *userdata) {
+        _cleanup_(varlink_flush_close_unrefp) 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 = 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_callb_and_log(vl, "io.systemd.Network.SetPersistentStorage", /* reply = */ NULL,
+                                     SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_BOOLEAN("Ready", ready)));
+}
+
 static int help(void) {
         _cleanup_free_ char *link = NULL;
         int r;
@@ -2876,9 +2982,11 @@ static int help(void) {
                "  reconfigure DEVICES... Reconfigure interfaces\n"
                "  reload                 Reload .network and .netdev files\n"
                "  edit FILES|DEVICES...  Edit network configuration files\n"
-               "  cat FILES|DEVICES...   Show network configuration files\n"
+               "  cat [FILES|DEVICES...] Show network configuration files\n"
                "  mask FILES...          Mask network configuration files\n"
                "  unmask FILES...        Unmask network configuration files\n"
+               "  persistent-storage BOOL\n"
+               "                         Notify systemd-networkd if persistent storage is ready\n"
                "\nOptions:\n"
                "  -h --help              Show this help\n"
                "     --version           Show package version\n"
@@ -3022,21 +3130,22 @@ static int parse_argv(int argc, char *argv[]) {
 
 static int networkctl_main(int argc, char *argv[]) {
         static const Verb verbs[] = {
-                { "list",        VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links          },
-                { "status",      VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,              link_status         },
-                { "lldp",        VERB_ANY, VERB_ANY, 0,                             link_lldp_status    },
-                { "label",       1,        1,        0,                             list_address_labels },
-                { "delete",      2,        VERB_ANY, 0,                             link_delete         },
-                { "up",          2,        VERB_ANY, 0,                             link_up_down        },
-                { "down",        2,        VERB_ANY, 0,                             link_up_down        },
-                { "renew",       2,        VERB_ANY, VERB_ONLINE_ONLY,              link_renew          },
-                { "forcerenew",  2,        VERB_ANY, VERB_ONLINE_ONLY,              link_force_renew    },
-                { "reconfigure", 2,        VERB_ANY, VERB_ONLINE_ONLY,              verb_reconfigure    },
-                { "reload",      1,        1,        VERB_ONLINE_ONLY,              verb_reload         },
-                { "edit",        2,        VERB_ANY, 0,                             verb_edit           },
-                { "cat",         2,        VERB_ANY, 0,                             verb_cat            },
-                { "mask",        2,        VERB_ANY, 0,                             verb_mask           },
-                { "unmask",      2,        VERB_ANY, 0,                             verb_unmask         },
+                { "list",               VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links              },
+                { "status",             VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,              link_status             },
+                { "lldp",               VERB_ANY, VERB_ANY, 0,                             link_lldp_status        },
+                { "label",              1,        1,        0,                             list_address_labels     },
+                { "delete",             2,        VERB_ANY, 0,                             link_delete             },
+                { "up",                 2,        VERB_ANY, 0,                             link_up_down            },
+                { "down",               2,        VERB_ANY, 0,                             link_up_down            },
+                { "renew",              2,        VERB_ANY, VERB_ONLINE_ONLY,              link_renew              },
+                { "forcerenew",         2,        VERB_ANY, VERB_ONLINE_ONLY,              link_force_renew        },
+                { "reconfigure",        2,        VERB_ANY, VERB_ONLINE_ONLY,              verb_reconfigure        },
+                { "reload",             1,        1,        VERB_ONLINE_ONLY,              verb_reload             },
+                { "edit",               2,        VERB_ANY, 0,                             verb_edit               },
+                { "cat",                1,        VERB_ANY, 0,                             verb_cat                },
+                { "mask",               2,        VERB_ANY, 0,                             verb_mask               },
+                { "unmask",             2,        VERB_ANY, 0,                             verb_unmask             },
+                { "persistent-storage", 2,        2,        0,                             verb_persistent_storage },
                 {}
         };