diff --git a/man/networkctl.xml b/man/networkctl.xml index 79a9fc1345..e85718269a 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/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c index 2cac77f472..3232680bbd 100644 --- a/src/libsystemd-network/lldp-neighbor.c +++ b/src/libsystemd-network/lldp-neighbor.c @@ -376,17 +376,6 @@ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_a return 0; } -int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(size, -EINVAL); - - *ret = LLDP_NEIGHBOR_RAW(n); - *size = n->raw_size; - - return 0; -} - int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { assert_return(n, -EINVAL); assert_return(type, -EINVAL); @@ -640,28 +629,6 @@ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret return 0; } -int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) { - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; - int r; - - assert_return(ret, -EINVAL); - assert_return(raw || raw_size <= 0, -EINVAL); - - n = lldp_neighbor_new(raw_size); - if (!n) - return -ENOMEM; - - memcpy_safe(LLDP_NEIGHBOR_RAW(n), raw, raw_size); - - r = lldp_neighbor_parse(n); - if (r < 0) - return r; - - *ret = TAKE_PTR(n); - - return r; -} - int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) { assert_return(n, -EINVAL); @@ -793,3 +760,31 @@ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_ *ret = triple_timestamp_by_clock(&n->timestamp, clock); return 0; } + +int lldp_neighbor_build_json(sd_lldp_neighbor *n, JsonVariant **ret) { + const char *chassis_id = NULL, *port_id = NULL, *port_description = NULL, + *system_name = NULL, *system_description = NULL; + uint16_t cc = 0; + bool valid_cc; + + assert(n); + assert(ret); + + (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_port_description(n, &port_description); + (void) sd_lldp_neighbor_get_system_name(n, &system_name); + (void) sd_lldp_neighbor_get_system_description(n, &system_description); + + valid_cc = sd_lldp_neighbor_get_enabled_capabilities(n, &cc); + + return json_build(ret, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING_NON_EMPTY("ChassisID", chassis_id), + JSON_BUILD_PAIR_BYTE_ARRAY("RawChassisID", n->id.chassis_id, n->id.chassis_id_size), + JSON_BUILD_PAIR_STRING_NON_EMPTY("PortID", port_id), + JSON_BUILD_PAIR_BYTE_ARRAY("RawPortID", n->id.port_id, n->id.port_id_size), + JSON_BUILD_PAIR_STRING_NON_EMPTY("PortDescription", port_description), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemName", system_name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemDescription", system_description), + JSON_BUILD_PAIR_CONDITION(valid_cc, "EnabledCapabilities", JSON_BUILD_UNSIGNED(cc)))); +} diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h index 016286b17d..06ba4c7460 100644 --- a/src/libsystemd-network/lldp-neighbor.h +++ b/src/libsystemd-network/lldp-neighbor.h @@ -8,6 +8,7 @@ #include "sd-lldp-rx.h" #include "hash-funcs.h" +#include "json.h" #include "lldp-rx-internal.h" #include "time-util.h" @@ -90,3 +91,4 @@ sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size); int lldp_neighbor_parse(sd_lldp_neighbor *n); void lldp_neighbor_start_ttl(sd_lldp_neighbor *n); bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b); +int lldp_neighbor_build_json(sd_lldp_neighbor *n, JsonVariant **ret); diff --git a/src/libsystemd-network/lldp-rx-internal.h b/src/libsystemd-network/lldp-rx-internal.h index 83d0bc460d..e914c6bfc6 100644 --- a/src/libsystemd-network/lldp-rx-internal.h +++ b/src/libsystemd-network/lldp-rx-internal.h @@ -5,6 +5,7 @@ #include "sd-lldp-rx.h" #include "hashmap.h" +#include "json.h" #include "network-common.h" #include "prioq.h" @@ -36,6 +37,8 @@ struct sd_lldp_rx { const char* lldp_rx_event_to_string(sd_lldp_rx_event_t e) _const_; sd_lldp_rx_event_t lldp_rx_event_from_string(const char *s) _pure_; +int lldp_rx_build_neighbors_json(sd_lldp_rx *lldp_rx, JsonVariant **ret); + #define log_lldp_rx_errno(lldp_rx, error, fmt, ...) \ log_interface_prefix_full_errno( \ "LLDP Rx: ", \ diff --git a/src/libsystemd-network/sd-lldp-rx.c b/src/libsystemd-network/sd-lldp-rx.c index 2fc9a55323..74000ffb8b 100644 --- a/src/libsystemd-network/sd-lldp-rx.c +++ b/src/libsystemd-network/sd-lldp-rx.c @@ -10,6 +10,7 @@ #include "ether-addr-util.h" #include "event-util.h" #include "fd-util.h" +#include "json.h" #include "lldp-neighbor.h" #include "lldp-network.h" #include "lldp-rx-internal.h" @@ -490,6 +491,30 @@ int sd_lldp_rx_get_neighbors(sd_lldp_rx *lldp_rx, sd_lldp_neighbor ***ret) { return k; } +int lldp_rx_build_neighbors_json(sd_lldp_rx *lldp_rx, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + int r; + + assert(lldp_rx); + assert(ret); + + sd_lldp_neighbor *n; + HASHMAP_FOREACH(n, lldp_rx->neighbor_by_id) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + r = lldp_neighbor_build_json(n, &w); + if (r < 0) + return r; + + r = json_variant_append_array(&v, w); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + int sd_lldp_rx_set_neighbors_max(sd_lldp_rx *lldp_rx, uint64_t m) { assert_return(lldp_rx, -EINVAL); assert_return(m > 0, -EINVAL); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 61fa0ed556..686d83b06b 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" @@ -93,11 +92,10 @@ JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_drop_in, freep); -static int check_netns_match(void) { - struct stat st; - uint64_t id; - JsonVariant *reply = NULL; +static int varlink_connect_networkd(Varlink **ret_varlink) { _cleanup_(varlink_unrefp) Varlink *vl = NULL; + JsonVariant *reply; + uint64_t id; int r; r = varlink_connect_address(&vl, "/run/systemd/netif/io.systemd.Network"); @@ -117,18 +115,21 @@ static int check_netns_match(void) { if (r < 0) return r; - if (id == 0) { + 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 (id != st.st_ino) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), + "networkctl must be invoked in same network namespace as systemd-networkd.service."); } - 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; } @@ -162,7 +163,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(); + r = varlink_connect_networkd(/* ret_varlink = */ NULL); if (r < 0) return r; } else @@ -1311,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) { @@ -1683,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, @@ -1698,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); @@ -2316,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; @@ -2424,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; @@ -2448,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) @@ -2464,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; } @@ -2472,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', }; @@ -2522,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(); @@ -2553,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); @@ -2613,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; diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 6304ead97a..650e94c313 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -272,7 +272,6 @@ static Link *link_free(Link *link) { free(link->driver); unlink_and_free(link->lease_file); - unlink_and_free(link->lldp_file); unlink_and_free(link->state_file); sd_device_unref(link->dev); @@ -2653,7 +2652,7 @@ static Link *link_drop_or_unref(Link *link) { DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_drop_or_unref); static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { - _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL, *lease_file = NULL, *lldp_file = NULL; + _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL, *lease_file = NULL; _cleanup_(link_drop_or_unrefp) Link *link = NULL; unsigned short iftype; int r, ifindex; @@ -2694,9 +2693,6 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { if (asprintf(&lease_file, "/run/systemd/netif/leases/%d", ifindex) < 0) return log_oom_debug(); - - if (asprintf(&lldp_file, "/run/systemd/netif/lldp/%d", ifindex) < 0) - return log_oom_debug(); } link = new(Link, 1); @@ -2719,7 +2715,6 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { .state_file = TAKE_PTR(state_file), .lease_file = TAKE_PTR(lease_file), - .lldp_file = TAKE_PTR(lldp_file), .n_dns = UINT_MAX, .dns_default_route = -1, diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 0167b1988d..b3e8bb001f 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -181,7 +181,6 @@ typedef struct Link { /* This is about LLDP reception */ sd_lldp_rx *lldp_rx; - char *lldp_file; /* This is about LLDP transmission */ sd_lldp_tx *lldp_tx; diff --git a/src/network/networkd-lldp-rx.c b/src/network/networkd-lldp-rx.c index c45d3e32d7..f74485488e 100644 --- a/src/network/networkd-lldp-rx.c +++ b/src/network/networkd-lldp-rx.c @@ -52,8 +52,6 @@ static void lldp_rx_handler(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_ll Link *link = ASSERT_PTR(userdata); int r; - (void) link_lldp_save(link); - if (link->lldp_tx && event == SD_LLDP_RX_EVENT_ADDED) { /* If we received information about a new neighbor, restart the LLDP "fast" logic */ @@ -104,70 +102,3 @@ int link_lldp_rx_configure(Link *link) { return 0; } - -int link_lldp_save(Link *link) { - _cleanup_(unlink_and_freep) char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - sd_lldp_neighbor **l = NULL; - int n = 0, r, i; - - assert(link); - - if (isempty(link->lldp_file)) - return 0; /* Do not update state file when running in test mode. */ - - if (!link->lldp_rx) { - (void) unlink(link->lldp_file); - return 0; - } - - r = sd_lldp_rx_get_neighbors(link->lldp_rx, &l); - if (r < 0) - return r; - if (r == 0) { - (void) unlink(link->lldp_file); - return 0; - } - - n = r; - - r = fopen_temporary(link->lldp_file, &f, &temp_path); - if (r < 0) - goto finish; - - (void) fchmod(fileno(f), 0644); - - for (i = 0; i < n; i++) { - const void *p; - le64_t u; - size_t sz; - - r = sd_lldp_neighbor_get_raw(l[i], &p, &sz); - if (r < 0) - goto finish; - - u = htole64(sz); - fwrite(&u, 1, sizeof(u), f); - fwrite(p, 1, sz, f); - } - - r = fflush_and_check(f); - if (r < 0) - goto finish; - - r = conservative_rename(temp_path, link->lldp_file); - if (r < 0) - goto finish; - -finish: - if (r < 0) - log_link_error_errno(link, r, "Failed to save LLDP data to %s: %m", link->lldp_file); - - if (l) { - for (i = 0; i < n; i++) - sd_lldp_neighbor_unref(l[i]); - free(l); - } - - return r; -} diff --git a/src/network/networkd-lldp-rx.h b/src/network/networkd-lldp-rx.h index 22f6602bd0..75c9f8ca86 100644 --- a/src/network/networkd-lldp-rx.h +++ b/src/network/networkd-lldp-rx.h @@ -14,7 +14,6 @@ typedef enum LLDPMode { } LLDPMode; int link_lldp_rx_configure(Link *link); -int link_lldp_save(Link *link); const char* lldp_mode_to_string(LLDPMode m) _const_; LLDPMode lldp_mode_from_string(const char *s) _pure_; diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 8aefb686c6..0c54e86aea 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -2,6 +2,7 @@ #include +#include "lldp-rx-internal.h" #include "networkd-manager-varlink.h" #include "varlink.h" #include "varlink-io.systemd.Network.h" @@ -56,6 +57,113 @@ static int vl_method_get_namespace_id(Varlink *link, JsonVariant *parameters, Va JSON_BUILD_PAIR_CONDITION(nsid != UINT32_MAX, "NamespaceNSID", JSON_BUILD_UNSIGNED(nsid)))); } +typedef struct InterfaceInfo { + int ifindex; + const char *ifname; +} InterfaceInfo; + +static int dispatch_interface(Varlink *vlink, JsonVariant *parameters, Manager *manager, Link **ret) { + static const JsonDispatch dispatch_table[] = { + { "InterfaceIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(InterfaceInfo, ifindex), 0 }, + { "InterfaceName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(InterfaceInfo, ifname), 0 }, + {} + }; + + InterfaceInfo info = {}; + Link *link = NULL; + int r; + + assert(vlink); + assert(manager); + + r = varlink_dispatch(vlink, parameters, dispatch_table, &info); + if (r != 0) + return r; + + if (info.ifindex < 0) + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceIndex")); + if (info.ifindex > 0 && link_get_by_index(manager, info.ifindex, &link) < 0) + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceIndex")); + if (info.ifname) { + Link *link_by_name; + + if (link_get_by_name(manager, info.ifname, &link_by_name) < 0) + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceName")); + + if (link && link_by_name != link) + /* If both arguments are specified, then these must be consistent. */ + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceName")); + + link = link_by_name; + } + + /* If neither InterfaceIndex nor InterfaceName specified, this function returns NULL. */ + *ret = link; + return 0; +} + +static int link_append_lldp_neighbors(Link *link, JsonVariant *v, JsonVariant **array) { + assert(link); + assert(array); + + return json_variant_append_arrayb(array, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_INTEGER("InterfaceIndex", link->ifindex), + JSON_BUILD_PAIR_STRING("InterfaceName", link->ifname), + JSON_BUILD_PAIR_STRV_NON_EMPTY("InterfaceAlternativeNames", link->alternative_names), + JSON_BUILD_PAIR_CONDITION(json_variant_is_blank_array(v), "Neighbors", JSON_BUILD_EMPTY_ARRAY), + JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_array(v), "Neighbors", JSON_BUILD_VARIANT(v)))); +} + +static int vl_method_get_lldp_neighbors(Varlink *vlink, JsonVariant *parameters, VarlinkMethodFlags flags, Manager *manager) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + Link *link = NULL; + int r; + + assert(vlink); + assert(manager); + + r = dispatch_interface(vlink, parameters, manager, &link); + if (r != 0) + return r; + + if (link) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (link->lldp_rx) { + r = lldp_rx_build_neighbors_json(link->lldp_rx, &v); + if (r < 0) + return r; + } + + r = link_append_lldp_neighbors(link, v, &array); + if (r < 0) + return r; + } else + HASHMAP_FOREACH(link, manager->links_by_index) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (!link->lldp_rx) + continue; + + r = lldp_rx_build_neighbors_json(link->lldp_rx, &v); + if (r < 0) + return r; + + if (json_variant_is_blank_array(v)) + continue; + + r = link_append_lldp_neighbors(link, v, &array); + if (r < 0) + return r; + } + + return varlink_replyb(vlink, + 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)))); +} + int manager_connect_varlink(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; @@ -78,7 +186,8 @@ int manager_connect_varlink(Manager *m) { r = varlink_server_bind_method_many( s, "io.systemd.Network.GetStates", vl_method_get_states, - "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id); + "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, + "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index 1cd23bb4ca..117db0af6c 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -583,8 +583,6 @@ static int link_save(Link *link) { if (link->state == LINK_STATE_LINGER) return 0; - link_lldp_save(link); - admin_state = link_state_to_string(link->state); assert(admin_state); diff --git a/src/network/networkd.c b/src/network/networkd.c index 3384c7c3ea..69a28647c8 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -72,8 +72,7 @@ static int run(int argc, char *argv[]) { * to support old kernels not supporting AmbientCapabilities=. */ FOREACH_STRING(p, "/run/systemd/netif/links/", - "/run/systemd/netif/leases/", - "/run/systemd/netif/lldp/") { + "/run/systemd/netif/leases/") { r = mkdir_safe_label(p, 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); if (r < 0) log_warning_errno(r, "Could not create directory '%s': %m", p); diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c index feb1e52feb..986f2cfc69 100644 --- a/src/shared/varlink-io.systemd.Network.c +++ b/src/shared/varlink-io.systemd.Network.c @@ -16,8 +16,35 @@ static VARLINK_DEFINE_METHOD( VARLINK_DEFINE_OUTPUT(NamespaceId, VARLINK_INT, 0), VARLINK_DEFINE_OUTPUT(NamespaceNSID, VARLINK_INT, VARLINK_NULLABLE)); +static VARLINK_DEFINE_STRUCT_TYPE( + LLDPNeighbor, + VARLINK_DEFINE_FIELD(ChassisID, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(RawChassisID, VARLINK_INT, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(PortID, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(RawPortID, VARLINK_INT, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(PortDescription, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(SystemName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(SystemDescription, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(EnabledCapabilities, VARLINK_INT, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_STRUCT_TYPE( + LLDPNeighborsByInterface, + VARLINK_DEFINE_FIELD(InterfaceIndex, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(InterfaceName, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(InterfaceAlternativeNames, VARLINK_STRING, VARLINK_ARRAY|VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD_BY_TYPE(Neighbors, LLDPNeighbor, VARLINK_ARRAY)); + +static VARLINK_DEFINE_METHOD( + GetLLDPNeighbors, + VARLINK_DEFINE_INPUT(InterfaceIndex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(InterfaceName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(Neighbors, LLDPNeighborsByInterface, VARLINK_ARRAY)); + VARLINK_DEFINE_INTERFACE( io_systemd_Network, "io.systemd.Network", &vl_method_GetStates, - &vl_method_GetNamespaceId); + &vl_method_GetNamespaceId, + &vl_method_GetLLDPNeighbors, + &vl_type_LLDPNeighbor, + &vl_type_LLDPNeighborsByInterface); diff --git a/src/systemd/sd-lldp-rx.h b/src/systemd/sd-lldp-rx.h index 504d7f59c1..a7e1a9f376 100644 --- a/src/systemd/sd-lldp-rx.h +++ b/src/systemd/sd-lldp-rx.h @@ -68,7 +68,6 @@ int sd_lldp_rx_set_filter_address(sd_lldp_rx *lldp_rx, const struct ether_addr * int sd_lldp_rx_get_neighbors(sd_lldp_rx *lldp_rx, sd_lldp_neighbor ***neighbors); -int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size); sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n); sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); @@ -76,7 +75,6 @@ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address); int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address); int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret); -int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); /* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index e194f212ee..ed9a051302 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -5332,6 +5332,39 @@ class NetworkdLLDPTests(unittest.TestCase, Utilities): else: self.fail() + # With interface name + output = networkctl('lldp', 'veth99'); + print(output) + self.assertRegex(output, r'veth99 .* veth-peer') + + # With interface name pattern + output = networkctl('lldp', 've*9'); + print(output) + self.assertRegex(output, r'veth99 .* veth-peer') + + # json format + output = networkctl('--json=short', 'lldp') + print(output) + self.assertIn('"InterfaceName":"veth99"', output) + self.assertIn('"PortID":"veth-peer"', output) + + # json format with interface name + output = networkctl('--json=short', 'lldp', 'veth99') + print(output) + self.assertIn('"InterfaceName":"veth99"', output) + self.assertIn('"PortID":"veth-peer"', output) + + # json format with interface name pattern + output = networkctl('--json=short', 'lldp', 've*9') + print(output) + self.assertIn('"InterfaceName":"veth99"', output) + self.assertIn('"PortID":"veth-peer"', output) + + # LLDP neighbors in status + output = networkctl_status('veth99') + print(output) + self.assertRegex(output, r'Connected To: .* on port veth-peer') + class NetworkdRATests(unittest.TestCase, Utilities): def setUp(self): diff --git a/tmpfiles.d/systemd-network.conf b/tmpfiles.d/systemd-network.conf index b30bc914a5..24197555ee 100644 --- a/tmpfiles.d/systemd-network.conf +++ b/tmpfiles.d/systemd-network.conf @@ -10,4 +10,3 @@ d /run/systemd/netif 0755 systemd-network systemd-network - d /run/systemd/netif/links 0755 systemd-network systemd-network - d /run/systemd/netif/leases 0755 systemd-network systemd-network - -d /run/systemd/netif/lldp 0755 systemd-network systemd-network -