]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
networkctl: use varlink method to dump LLDP neighbors
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 25 Feb 2024 06:31:23 +0000 (15:31 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 1 Mar 2024 00:40:26 +0000 (09:40 +0900)
`networkctl lldp` and `networkctl status INTERFACE` now use varlink
call to the networkd to query LLDP neighbors.

Then, this allows to dump LLDP neighbors in JSON format.

Co-authored-by: Tomáš Pecka <tomas.pecka@cesnet.cz>
man/networkctl.xml
src/network/networkctl.c

index 79a9fc1345420f8f2838df61a3c4e664b8798ceb..e85718269a5e07ccfacf2c39562b99e7474b4f03 100644 (file)
           details.</para>
 
           <para>Produces output similar to:
-          <programlisting>LINK             CHASSIS ID        SYSTEM NAME      CAPS        PORT ID           PORT DESCRIPTION
-enp0s25          00:e0:4c:00:00:00 GS1900           ..b........ 2                 Port #2
+          <programlisting>LINK    SYSTEM-NAME SYSTEM-DESCRIPTION CHASSIS-ID        PORT-ID PORT-DESCRIPTION CAPS
+enp0s25 GS1900      -                  00:e0:4c:00:00:00 2       Port #2           ..b........
 
 Capability Flags:
 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)
 
-1 neighbors listed.</programlisting></para>
+1 neighbor(s) listed.</programlisting></para>
 
           <xi:include href="version-info.xml" xpointer="v219"/>
         </listitem>
index fefcbfe82957293b99cfac35d3cdc0a354ff5345..686d83b06b4da39921bd7cf365ab9eb7d924e455 100644 (file)
@@ -15,7 +15,6 @@
 #include "sd-device.h"
 #include "sd-dhcp-client.h"
 #include "sd-hwdb.h"
-#include "sd-lldp-rx.h"
 #include "sd-netlink.h"
 #include "sd-network.h"
 
@@ -1313,96 +1312,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;
+        JsonVariant *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);
+        json_variant_unref(p->v);
 }
 
-static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) {
+static const JsonDispatch interface_info_dispatch_table[] = {
+        { "InterfaceIndex",            _JSON_VARIANT_TYPE_INVALID, json_dispatch_int,          offsetof(InterfaceInfo, ifindex),  JSON_MANDATORY },
+        { "InterfaceName",             JSON_VARIANT_STRING,        json_dispatch_const_string, offsetof(InterfaceInfo, ifname),   JSON_MANDATORY },
+        { "InterfaceAlternativeNames", JSON_VARIANT_ARRAY,         json_dispatch_strv,         offsetof(InterfaceInfo, altnames), 0              },
+        { "Neighbors",                 JSON_VARIANT_ARRAY,         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 JsonDispatch lldp_neighbor_dispatch_table[] = {
+        { "ChassisID",           JSON_VARIANT_STRING,        json_dispatch_const_string, offsetof(LLDPNeighborInfo, chassis_id),         0 },
+        { "PortID",              JSON_VARIANT_STRING,        json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_id),            0 },
+        { "PortDescription",     JSON_VARIANT_STRING,        json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_description),   0 },
+        { "SystemName",          JSON_VARIANT_STRING,        json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_name),        0 },
+        { "SystemDescription",   JSON_VARIANT_STRING,        json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_description), 0 },
+        { "EnabledCapabilities", _JSON_VARIANT_TYPE_INVALID, 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;
+        JsonVariant *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,
+                                  JSON_BUILD_OBJECT(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;
+        JsonVariant *i;
+        JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(reply, "Neighbors")) {
+                _cleanup_(interface_info_done) InterfaceInfo info = {};
 
-                r = next_lldp_neighbor(f, &n);
+                r = json_dispatch(i, interface_info_dispatch_table, JSON_LOG|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();
+                JsonVariant *neighbor;
+                JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) {
+                        LLDPNeighborInfo neighbor_info = {};
+
+                        r = json_dispatch(neighbor, lldp_neighbor_dispatch_table, JSON_LOG|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) {
@@ -1685,6 +1684,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,
@@ -1700,6 +1700,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);
@@ -2318,7 +2319,7 @@ static int link_status_one(
                         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;
 
@@ -2426,6 +2427,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_unrefp) Varlink *vl = NULL;
         _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
         int r, c;
 
@@ -2450,6 +2452,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)
@@ -2466,7 +2472,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;
         }
@@ -2474,7 +2480,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',
         };
@@ -2524,30 +2530,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(JsonVariant *reply, char * const *patterns) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL, *v = NULL;
+        int r;
+
+        assert(reply);
+
+        if (strv_isempty(patterns))
+                return json_variant_dump(reply, arg_json_format_flags, NULL, NULL);
+
+        /* Filter and dump the result. */
+
+        JsonVariant *i;
+        JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(reply, "Neighbors")) {
+                _cleanup_(interface_info_done) InterfaceInfo info = {};
+
+                r = json_dispatch(i, interface_info_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &info);
+                if (r < 0)
+                        return r;
+
+                if (!interface_match_pattern(&info, patterns))
+                        continue;
+
+                r = json_variant_append_array(&array, i);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to append json variant to array: %m");
+        }
+
+        r = json_build(&v,
+                JSON_BUILD_OBJECT(
+                        JSON_BUILD_PAIR_CONDITION(json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_EMPTY_ARRAY),
+                        JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_VARIANT(array))));
+        if (r < 0)
+                return log_error_errno(r, "Failed to build json varinat: %m");
+
+        return 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_unrefp) Varlink *vl = NULL;
         _cleanup_(table_unrefp) Table *table = NULL;
-        int r, c, m = 0;
-        uint16_t all = 0;
+        JsonVariant *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 != 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();
 
@@ -2555,53 +2625,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;
+        JsonVariant *i;
+        JSON_VARIANT_ARRAY_FOREACH(i, 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 = json_dispatch(i, interface_info_dispatch_table, JSON_LOG|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;
+                JsonVariant *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 = json_dispatch(neighbor, lldp_neighbor_dispatch_table, JSON_LOG|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);
 
@@ -2615,7 +2678,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;