diff --git a/man/networkctl.xml b/man/networkctl.xml index 79a9fc13454..e85718269a5 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -302,15 +302,15 @@ details. Produces output similar to: - LINK CHASSIS ID SYSTEM NAME CAPS PORT ID PORT DESCRIPTION -enp0s25 00:e0:4c:00:00:00 GS1900 ..b........ 2 Port #2 + 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. +1 neighbor(s) listed. diff --git a/src/network/networkctl.c b/src/network/networkctl.c index fefcbfe8295..686d83b06b4 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -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;