networkctl: use varlink method to dump LLDP neighbors

`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>
This commit is contained in:
Yu Watanabe 2024-02-25 15:31:23 +09:00
parent 1dbd2cc72a
commit 14a5c07afa
2 changed files with 191 additions and 128 deletions

View file

@ -302,15 +302,15 @@
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>

View 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)];
typedef struct InterfaceInfo {
int ifindex;
const char *ifname;
char **altnames;
JsonVariant *v;
} InterfaceInfo;
assert(ifindex >= 0);
assert(ret);
static void interface_info_done(InterfaceInfo *p) {
if (!p)
return;
xsprintf(p, "/run/systemd/netif/lldp/%i", ifindex);
f = fopen(p, "re");
if (!f)
return -errno;
*ret = TAKE_PTR(f);
return 0;
strv_free(p->altnames);
json_variant_unref(p->v);
}
static int next_lldp_neighbor(FILE *f, sd_lldp_neighbor **ret) {
_cleanup_free_ void *raw = NULL;
size_t l;
le64_t u;
int r;
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 },
{},
};
assert(f);
assert(ret);
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;
l = fread(&u, 1, sizeof(u), f);
if (l == 0 && feof(f))
return 0;
if (l != sizeof(u))
return -EBADMSG;
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 },
{},
};
/* 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;
r = sd_lldp_neighbor_from_raw(ret, raw, le64toh(u));
if (r < 0)
return r;
return 1;
}
static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) {
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 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_(table_unrefp) Table *table = NULL;
int r, c, m = 0;
uint16_t all = 0;
TableCell *cell;
static bool interface_match_pattern(const InterfaceInfo *info, char * const *patterns) {
assert(info);
r = sd_netlink_open(&rtnl);
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 connect to netlink: %m");
return log_error_errno(r, "Failed to build json varinat: %m");
c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links);
if (c < 0)
return c;
return json_variant_dump(v, arg_json_format_flags, NULL, NULL);
}
static int link_lldp_status(int argc, char *argv[], void *userdata) {
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
JsonVariant *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 != 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);
assert_se(cell = table_get_cell(table, 0, 3));
table_set_minimum_width(table, cell, 11);
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);
FOREACH_ARRAY(link, links, c) {
_cleanup_fclose_ FILE *f = NULL;
/* Make the capabilities not truncated */
assert_se(cell = table_get_cell(table, 0, 7));
table_set_minimum_width(table, cell, 11);
r = open_lldp_neighbors(link->ifindex, &f);
if (r == -ENOENT)
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, strv_skip(argv, 1)))
continue;
if (r < 0) {
log_warning_errno(r, "Failed to open LLDP data for %i, ignoring: %m", link->ifindex);
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;