On 31 Oct 2025, at 2:09, Changliang Wu wrote:
> New appctl 'lldp/neighbor' displays lldp neighbor information. > Support json output with --format json --pretty Thanks for following up. See my review comments inline below. //Eelco > Signed-off-by: Changliang Wu <[email protected]> > --- > NEWS | 2 + > lib/lldp/lldpd-structs.h | 4 +- > lib/ovs-lldp.c | 578 ++++++++++++++++++++++++++++++++++++- > vswitchd/ovs-vswitchd.8.in | 4 + > 4 files changed, 581 insertions(+), 7 deletions(-) > > diff --git a/NEWS b/NEWS > index d24513b46..8259fdd2e 100644 > --- a/NEWS > +++ b/NEWS > @@ -9,6 +9,8 @@ Post-v3.6.0 > * New ovsdb_idl_txn_assert_read_only() interface to mark transactions > as read-only and trigger assertion failure when application attempts > to modify the database data through this transaction. > + - ovs-vsctl: > + * Added 'lldp/neighbor' command that displays lldp neighbor information. > > > v3.6.0 - 18 Aug 2025 > diff --git a/lib/lldp/lldpd-structs.h b/lib/lldp/lldpd-structs.h > index fe5d5f9f8..9a83d5499 100644 > --- a/lib/lldp/lldpd-structs.h > +++ b/lib/lldp/lldpd-structs.h > @@ -65,7 +65,7 @@ struct lldpd_chassis { > u_int8_t c_protocol; /* Protocol used to get this chassis */ > u_int8_t c_id_subtype; > uint8_t *c_id; /* Typically an Ethernet address. */ > - int c_id_len; > + u_int32_t c_id_len; > char *c_name; > char *c_descr; > > @@ -94,7 +94,7 @@ struct lldpd_port { > */ > u_int8_t p_id_subtype; > char *p_id; > - int p_id_len; > + u_int32_t p_id_len; > char *p_descr; > u_int16_t p_mfs; > struct lldpd_aa_element_tlv p_element; > diff --git a/lib/ovs-lldp.c b/lib/ovs-lldp.c > index 152777248..d85845ba6 100644 > --- a/lib/ovs-lldp.c > +++ b/lib/ovs-lldp.c > @@ -36,7 +36,10 @@ > #include <stdlib.h> > #include "openvswitch/dynamic-string.h" > #include "flow.h" > +#include "openvswitch/json.h" > #include "openvswitch/list.h" > +#include "lldp/lldp-const.h" > +#include "lldp/lldp-tlv.h" > #include "lldp/lldpd.h" > #include "lldp/lldpd-structs.h" > #include "netdev.h" > @@ -57,6 +60,28 @@ VLOG_DEFINE_THIS_MODULE(ovs_lldp); > #define ETH_TYPE_LLDP 0x88cc > #define MINIMUM_ETH_PACKET_SIZE 68 > > +static char portid_subtype_str_map[LLDP_PORTID_SUBTYPE_MAX + 1][10] = { > + { "unknown" }, > + { "ifalias" }, > + { "unhandled" }, Here and below, “unhandled should not be displayed! The proper type should be displayed, as hex is displayed anyway for the value. > + { "mac" }, > + { "ip" }, > + { "ifname" }, > + { "unhandled" }, > + { "local" } > +}; > + > +static char chassisid_subtype_str_map[LLDP_CHASSISID_SUBTYPE_LOCAL + 1][10] > = { > + { "unknown" }, > + { "unhandled" }, > + { "ifalias" }, > + { "unhandled" }, > + { "mac" }, > + { "ip" }, > + { "ifname" }, > + { "local" } > +}; > + I think a special function for these is needed to ensure proper access is guaranteed. See below for more details. Here is an example: static const char * get_chassisid_subtype_str(u_int32_t type) { static const char *const chassisid_subtype_map[] = { "unknown", "unhandled", "ifalias", "unhandled", "mac", "ip", "ifname", "local", }; if (ARRAY_SIZE(chassisid_subtype_map)) { return "UNKNOWN"; } return chassisid_subtype_map[type]; } > #define AA_STATUS_MULTIPLE \ > AA_STATUS(ACTIVE,2,Active) \ > AA_STATUS(REJECT_GENERIC,3,Reject (Generic)) \ > @@ -193,11 +218,8 @@ aa_print_element_status_port(struct ds *ds, struct > lldpd_hardware *hw) > struct ds system = DS_EMPTY_INITIALIZER; > > if (port->p_chassis) { > - if (port->p_chassis->c_id_len > 0) { > - ds_put_hex_with_delimiter(&id, port->p_chassis->c_id, > - port->p_chassis->c_id_len, ":"); > - } > - > + ds_put_hex_with_delimiter(&id, port->p_chassis->c_id, > + port->p_chassis->c_id_len, ":"); > descr = port->p_chassis->c_descr; > } > > @@ -310,6 +332,508 @@ aa_print_isid_status(struct ds *ds, struct lldp *lldp) > OVS_REQUIRES(mutex) > } > } > > +static char * > +lldp_get_network_addr_string(char *port_id) I guess the comments in this function can be ignored, as this function should be rewritten to accept *ds and perform all manipulation directly on it, to avoid allocating and freeing memory. Some ds function for IP addresses already exist. > +{ > + char *ipaddress = NULL; size_t len; > + > + if (!port_id) { > + return NULL; > + } > + > + size_t len; Len should move up. > + switch (port_id[0]) { > + case LLDP_MGMT_ADDR_IP4: > + len = INET_ADDRSTRLEN + 1; > + break; > + > + case LLDP_MGMT_ADDR_IP6: > + len = INET6_ADDRSTRLEN + 1; > + break; > + > + default: Do return NULL here. > + len = 0; > + } > + > + if (len > 0) { As default returns NULL we can skip this check. > + ipaddress = xzalloc(len); > + memset(ipaddress, '\0', len); > + inet_ntop((port_id[0] == LLDP_MGMT_ADDR_IP4) ? AF_INET : AF_INET6, > + &port_id[1], ipaddress, len); > + } > + return ipaddress; > +} > + > +static void > +lldp_print_neighbor(struct ds *ds, struct lldp *lldp) OVS_REQUIRES(mutex) > +{ > + const char *none_str = "<None>"; > + bool is_first_neighbor = true; > + struct lldpd_hardware *hw; > + struct lldpd_port *port; > + > + if (!lldp->lldpd) { > + return; > + } > + > + LIST_FOR_EACH (hw, h_entries, &lldp->lldpd->g_hardware) { > + LIST_FOR_EACH (port, p_entries, &hw->h_rports) { > + char chassis_id_str[512]; > + char *ipaddress; > + char port_id_str[512]; > + > + if (!port->p_chassis) { > + continue; > + } > + > + if (!is_first_neighbor) { > + ds_put_format(ds, "\n"); > + }else{ There should be spaces around else. > + is_first_neighbor = false; > + } > + > + ds_put_format(ds, "Interface: %s\n", lldp->name); > + > + /* Basic TLV, Chassis ID (Type = 1). */ > + sprintf(chassis_id_str, "%s%s%s", "ChassisID[", > + chassisid_subtype_str_map[port->p_chassis->c_id_subtype], > + "]:"); It’s not safe to access it like this. The c_id_subtype is not validated, which could cause illegal access. Also, sprintf() is not a safe function; you should at least use snprintf(). I guess you cannot use the ds directly; however, I would still define chassis_id_str[] shorter so it can handle 20 characters (so 21, I guess). Also, I would make it a general buffer so you don’t need another one for port_id_str. > + ds_put_format(ds, " %-20s", chassis_id_str); > + switch (port->p_chassis->c_id_subtype) { > + case LLDP_CHASSISID_SUBTYPE_IFNAME: > + case LLDP_CHASSISID_SUBTYPE_IFALIAS: > + case LLDP_CHASSISID_SUBTYPE_LOCAL: > + ds_put_format( > + ds, "%.*s", > + (int) (port->p_chassis->c_id ? port->p_chassis->c_id_len > + : strlen(none_str)), > + port->p_chassis->c_id ? (char *) port->p_chassis->c_id > + : none_str); > + break; > + > + case LLDP_CHASSISID_SUBTYPE_LLADDR: > + ds_put_hex_with_delimiter(ds, port->p_chassis->c_id, > + port->p_chassis->c_id_len, ":"); > + break; > + > + case LLDP_CHASSISID_SUBTYPE_ADDR: > + ipaddress = lldp_get_network_addr_string( > + (char *) port->p_chassis->c_id); > + if (ipaddress) { > + ds_put_and_free_cstr(ds, ipaddress); > + } See pervious comment @ lldp_get_network_addr_string(), this should change to it will simply become: lldp_get_network_addr_string(ds, (char *) port->p_chassis->c_id)); > + break; > + > + default: > + ds_put_hex(ds, port->p_chassis->c_id, > + port->p_chassis->c_id_len); > + } > + ds_put_char(ds, '\n'); > + > + /* Basic TLV, Port ID (Type = 2). */ > + sprintf(port_id_str, "%s%s%s", "PortID[", > + portid_subtype_str_map[port->p_id_subtype], "]:"); See earlier comments about chassisid_subtype_str_map and about snprintf(). > + ds_put_format(ds, " %-20s", port_id_str); > + switch (port->p_id_subtype) { > + case LLDP_PORTID_SUBTYPE_IFNAME: > + case LLDP_PORTID_SUBTYPE_IFALIAS: > + case LLDP_PORTID_SUBTYPE_LOCAL: > + ds_put_format(ds, "%.*s", > + (int) (port->p_id ? port->p_id_len > + : strlen(none_str)), > + port->p_id ? port->p_id : none_str); > + break; > + > + case LLDP_PORTID_SUBTYPE_LLADDR: > + ds_put_hex_with_delimiter(ds, (uint8_t *) port->p_id, > + port->p_id_len, ":"); > + break; > + > + case LLDP_PORTID_SUBTYPE_ADDR: > + ipaddress = lldp_get_network_addr_string(port->p_id); > + if (ipaddress) { > + ds_put_and_free_cstr(ds, ipaddress); > + } > + break; > + > + default: > + ds_put_hex(ds, port->p_id, port->p_id_len); > + } > + ds_put_char(ds, '\n'); > + > + /* Basic TLV, Time To Live (Type = 3). */ > + ds_put_format(ds, " %-20s%d\n", "TTL:", port->p_chassis->c_ttl); > + > + /* Basic TLV, Port Description (Type = 4). */ > + ds_put_format(ds, " %-20s%s\n", "PortDescr:", > + !str_is_null_or_empty(port->p_descr) > + ? port->p_descr > + : none_str); > + > + /* Basic TLV, System Name (Type = 5). */ > + ds_put_format(ds, " %-20s%s\n", "SysName:", > + !str_is_null_or_empty(port->p_chassis->c_name) > + ? port->p_chassis->c_name > + : none_str); > + > + /* Basic TLV, System Description (Type = 6). */ > + ds_put_format(ds, " %-20s%s\n", "SysDescr:", > + !str_is_null_or_empty(port->p_chassis->c_descr) > + ? port->p_chassis->c_descr > + : none_str); > + > + /* Basic TLV, System Capabilities (Type = 7). */ > + if (port->p_chassis->c_cap_available & LLDP_CAP_BRIDGE) { > + ds_put_format(ds, " %-20sBridge, %s\n", "Capability:", > + port->p_chassis->c_cap_enabled & > LLDP_CAP_BRIDGE > + ? "on" > + : "off"); > + } I think capabilities should be displayed in a way it is more clear (and always display all capabilities possible). Also, check the naming against the spec, and see if you missed any bits. Capability: Bridge supported, enabled Capability: WIFI unsupported Capability: Repeater supported, disabled > + if (port->p_chassis->c_cap_available & LLDP_CAP_ROUTER) { > + ds_put_format(ds, " %-20sRouter, %s\n", "Capability:", > + port->p_chassis->c_cap_enabled & > LLDP_CAP_ROUTER > + ? "on" > + : "off"); > + } > + if (port->p_chassis->c_cap_available & LLDP_CAP_WLAN) { > + ds_put_format(ds, " %-20sWlan, %s\n", "Capability:", WLAN should be all capitals > + port->p_chassis->c_cap_enabled & LLDP_CAP_WLAN > + ? "on" > + : "off"); > + } > + if (port->p_chassis->c_cap_available & LLDP_CAP_STATION) { > + ds_put_format(ds, " %-20sStation, %s\n", "Capability:", > + port->p_chassis->c_cap_enabled & > LLDP_CAP_STATION > + ? "on" > + : "off"); > + } > + if (port->p_chassis->c_cap_available & LLDP_CAP_REPEATER) { > + ds_put_format( > + ds, " %-20sRepeater, %s\n", "Capability:", > + port->p_chassis->c_cap_enabled & LLDP_CAP_REPEATER > + ? "on" > + : "off"); > + } > + if (port->p_chassis->c_cap_available & LLDP_CAP_TELEPHONE) { > + ds_put_format( > + ds, " %-20sTelephone, %s\n", "Capability:", > + port->p_chassis->c_cap_enabled & LLDP_CAP_TELEPHONE > + ? "on" > + : "off"); > + } > + if (port->p_chassis->c_cap_available & LLDP_CAP_DOCSIS) { > + ds_put_format(ds, " %-20sDocsis, %s\n", "Capability:", DOCSIS should be all capitals > + port->p_chassis->c_cap_enabled & > LLDP_CAP_DOCSIS > + ? "on" > + : "off"); > + } > + if (port->p_chassis->c_cap_available & LLDP_CAP_OTHER) { > + ds_put_format(ds, " %-20sOther, %s\n", "Capability:", > + port->p_chassis->c_cap_enabled & LLDP_CAP_OTHER > + ? "on" > + : "off"); > + } > + > + /* Basic TLV, Management Address (Type = 8). */ > + struct lldpd_mgmt *mgmt; > + LIST_FOR_EACH (mgmt, m_entries, &port->p_chassis->c_mgmt) { > + struct in6_addr ip; > + > + switch (mgmt->m_family) { > + case LLDPD_AF_IPV4: > + in6_addr_set_mapped_ipv4(&ip, mgmt->m_addr.inet.s_addr); > + break; > + > + case LLDPD_AF_IPV6: > + ip = mgmt->m_addr.inet6; > + break; > + > + default: Should we dump hex for other types? > + continue; > + } > + ds_put_format(ds, " %-20s", "MgmtIP:"); > + ipv6_format_mapped(&ip, ds); > + ds_put_format(ds, "\n"); > + ds_put_format(ds, " %-20s%d\n", > + "MgmtIface:", mgmt->m_iface); > + } > + } > + } > +} > + > +static void > +lldp_print_neighbor_json(struct json *neighbor_json, struct lldp *lldp) > + OVS_REQUIRES(mutex) > +{ > + struct lldpd_hardware *hw; > + struct lldpd_port *port; > + > + if (!lldp->lldpd) { > + return; > + } > + > + struct json *neighbor_array_json = NULL; > + > + LIST_FOR_EACH (hw, h_entries, &lldp->lldpd->g_hardware) { > + LIST_FOR_EACH (port, p_entries, &hw->h_rports) { > + struct json *chassis_mgmt_iface_json = json_array_create_empty(); > + struct json *chassis_capability_json = json_array_create_empty(); > + struct json *chassis_mgmt_ip_json = json_array_create_empty(); > + struct json *neighbor_item_wrap_json = json_object_create(); > + struct json *neighbor_item_json = json_object_create(); > + struct json *chassis_sys_json = json_object_create(); > + struct json *chassis_id_json = json_object_create(); > + struct json *port_id_json = json_object_create(); > + struct json *chassis_json = json_object_create(); > + struct json *port_json = json_object_create(); You create all the object here, but there are cases where you are not using them, so the memory gets lost. > + struct ds chassis_id = DS_EMPTY_INITIALIZER; > + struct json *chassis_capability_item_json; > + struct ds port_id = DS_EMPTY_INITIALIZER; > + struct lldpd_mgmt *mgmt; > + char *ipaddress; > + > + if (!port->p_chassis) { For example, if this happens all memory is lost… > + continue; > + } > + > + /* Basic TLV, Chassis ID (Type = 1). */ > + switch (port->p_chassis->c_id_subtype) { > + case LLDP_CHASSISID_SUBTYPE_IFNAME: > + case LLDP_CHASSISID_SUBTYPE_IFALIAS: > + case LLDP_CHASSISID_SUBTYPE_LOCAL: > + if (!str_is_null_or_empty((char *) port->p_chassis->c_id)) { > + ds_put_format(&chassis_id, "%.*s", > + port->p_chassis->c_id_len, > + port->p_chassis->c_id); > + } > + break; > + > + case LLDP_CHASSISID_SUBTYPE_LLADDR: > + ds_put_hex_with_delimiter(&chassis_id, port->p_chassis->c_id, > + port->p_chassis->c_id_len, ":"); > + break; > + > + case LLDP_CHASSISID_SUBTYPE_ADDR: > + ipaddress = lldp_get_network_addr_string( > + (char *) port->p_chassis->c_id); > + if (ipaddress) { > + ds_put_and_free_cstr(&chassis_id, ipaddress); > + } > + break; > + > + default: > + ds_put_hex(&chassis_id, port->p_id, port->p_id_len); > + } > + json_object_put( > + chassis_id_json, "value", > + json_string_create_nocopy(ds_steal_cstr(&chassis_id))); > + json_object_put( > + chassis_id_json, "type", > + json_string_create( > + > chassisid_subtype_str_map[port->p_chassis->c_id_subtype])); See earlier comment about illegal access. > + json_object_put(chassis_sys_json, "id", chassis_id_json); > + > + /* Basic TLV, Port ID (Type = 2). */ > + json_object_put(port_id_json, "type", > + json_string_create( > + portid_subtype_str_map[port->p_id_subtype])); See earlier comment about illegal access. > + switch (port->p_id_subtype) { > + case LLDP_PORTID_SUBTYPE_IFNAME: > + case LLDP_PORTID_SUBTYPE_IFALIAS: > + case LLDP_PORTID_SUBTYPE_LOCAL: > + if (!str_is_null_or_empty(port->p_id)) { > + ds_put_format(&port_id, "%.*s", port->p_id_len, > + port->p_id); > + } > + break; > + > + case LLDP_PORTID_SUBTYPE_LLADDR: > + ds_put_hex_with_delimiter(&port_id, port->p_id, > port->p_id_len, > + ":"); > + break; > + > + case LLDP_PORTID_SUBTYPE_ADDR: > + ipaddress = lldp_get_network_addr_string(port->p_id); > + if (ipaddress) { > + ds_put_and_free_cstr(&port_id, ipaddress); > + } > + break; > + > + default: > + ds_put_hex(&port_id, port->p_id, port->p_id_len); > + } > + json_object_put( > + port_id_json, "value", > + json_string_create_nocopy(ds_steal_cstr(&port_id))); > + json_object_put(port_json, "id", port_id_json); > + > + /* Basic TLV, Time To Live (Type = 3). */ > + json_object_put(port_json, "ttl", > + json_integer_create(port->p_chassis->c_ttl)); > + > + /* Basic TLV, Port Description (Type = 4). */ > + if (!str_is_null_or_empty(port->p_descr)) { > + json_object_put(port_json, "desc", > + json_string_create(port->p_descr)); > + } > + > + /* Basic TLV, System Name (Type = 5). */ > + if (!str_is_null_or_empty(port->p_chassis->c_name)) { > + json_object_put(chassis_json, port->p_chassis->c_name, > + chassis_sys_json); > + } > + > + /* Basic TLV, System Description (Type = 6). */ > + if (!str_is_null_or_empty(port->p_chassis->c_descr)) { > + json_object_put(chassis_sys_json, "descr", > + > json_string_create(port->p_chassis->c_descr)); > + } > + > + /* Basic TLV, System Capabilities (Type = 7). */ > + if (port->p_chassis->c_cap_available & LLDP_CAP_BRIDGE) { > + chassis_capability_item_json = json_object_create(); > + json_object_put(chassis_capability_item_json, "type", > + json_string_create("Bridge")); > + json_object_put( > + chassis_capability_item_json, "enabled", > + json_boolean_create(port->p_chassis->c_cap_enabled & > + LLDP_CAP_BRIDGE)); > + json_array_add(chassis_capability_json, > + chassis_capability_item_json); > + } > + > + if (port->p_chassis->c_cap_available & LLDP_CAP_ROUTER) { > + chassis_capability_item_json = json_object_create(); > + json_object_put(chassis_capability_item_json, "type", > + json_string_create("Router")); > + json_object_put( > + chassis_capability_item_json, "enabled", > + json_boolean_create(port->p_chassis->c_cap_enabled & > + LLDP_CAP_ROUTER)); > + json_array_add(chassis_capability_json, > + chassis_capability_item_json); > + } > + > + if (port->p_chassis->c_cap_available & LLDP_CAP_WLAN) { > + chassis_capability_item_json = json_object_create(); > + json_object_put(chassis_capability_item_json, "type", > + json_string_create("Wlan")); > + json_object_put( > + chassis_capability_item_json, "enabled", > + json_boolean_create(port->p_chassis->c_cap_enabled & > + LLDP_CAP_WLAN)); > + json_array_add(chassis_capability_json, > + chassis_capability_item_json); > + } > + > + if (port->p_chassis->c_cap_available & LLDP_CAP_STATION) { > + chassis_capability_item_json = json_object_create(); > + json_object_put(chassis_capability_item_json, "type", > + json_string_create("Station")); > + json_object_put( > + chassis_capability_item_json, "enabled", > + json_boolean_create(port->p_chassis->c_cap_enabled & > + LLDP_CAP_STATION)); > + json_array_add(chassis_capability_json, > + chassis_capability_item_json); > + } > + > + if (port->p_chassis->c_cap_available & LLDP_CAP_REPEATER) { > + chassis_capability_item_json = json_object_create(); > + json_object_put(chassis_capability_item_json, "type", > + json_string_create("Repeater")); > + json_object_put( > + chassis_capability_item_json, "enabled", > + json_boolean_create(port->p_chassis->c_cap_enabled & > + LLDP_CAP_REPEATER)); > + json_array_add(chassis_capability_json, > + chassis_capability_item_json); > + } > + > + if (port->p_chassis->c_cap_available & LLDP_CAP_TELEPHONE) { > + chassis_capability_item_json = json_object_create(); > + json_object_put(chassis_capability_item_json, "type", > + json_string_create("Telephone")); > + json_object_put( > + chassis_capability_item_json, "enabled", > + json_boolean_create(port->p_chassis->c_cap_enabled & > + LLDP_CAP_TELEPHONE)); > + json_array_add(chassis_capability_json, > + chassis_capability_item_json); > + } > + > + if (port->p_chassis->c_cap_available & LLDP_CAP_DOCSIS) { > + chassis_capability_item_json = json_object_create(); > + json_object_put(chassis_capability_item_json, "type", > + json_string_create("Docsis")); > + json_object_put( > + chassis_capability_item_json, "enabled", > + json_boolean_create(port->p_chassis->c_cap_enabled & > + LLDP_CAP_DOCSIS)); > + json_array_add(chassis_capability_json, > + chassis_capability_item_json); > + } > + > + if (port->p_chassis->c_cap_available & LLDP_CAP_OTHER) { > + chassis_capability_item_json = json_object_create(); > + json_object_put(chassis_capability_item_json, "type", > + json_string_create("Other")); > + json_object_put( > + chassis_capability_item_json, "enabled", > + json_boolean_create(port->p_chassis->c_cap_enabled & > + LLDP_CAP_OTHER)); > + json_array_add(chassis_capability_json, > + chassis_capability_item_json); > + } > + > + json_object_put(chassis_sys_json, "capability", > + chassis_capability_json); > + > + /* Basic TLV, Management Address (Type = 8). */ > + LIST_FOR_EACH (mgmt, m_entries, &port->p_chassis->c_mgmt) { > + char addr_str[INET6_ADDRSTRLEN]; > + struct in6_addr ip; > + > + switch (mgmt->m_family) { > + case LLDPD_AF_IPV4: > + in6_addr_set_mapped_ipv4(&ip, mgmt->m_addr.inet.s_addr); > + break; > + > + case LLDPD_AF_IPV6: > + ip = mgmt->m_addr.inet6; > + break; > + > + default: > + continue; > + } > + > + ipv6_string_mapped(addr_str, &ip); > + json_array_add(chassis_mgmt_ip_json, > + json_string_create(addr_str)); > + json_array_add(chassis_mgmt_iface_json, > + json_integer_create(mgmt->m_iface)); > + } > + json_object_put(chassis_sys_json, "mgmt-ip", > chassis_mgmt_ip_json); > + json_object_put(chassis_sys_json, "mgmt-iface", > + chassis_mgmt_iface_json); > + > + json_object_put(neighbor_item_json, "chassis", chassis_json); > + json_object_put(neighbor_item_json, "port", port_json); > + > + json_object_put(neighbor_item_wrap_json, lldp->name, > + neighbor_item_json); > + > + if (!neighbor_array_json) { > + neighbor_array_json = json_array_create_empty(); > + } > + json_array_add(neighbor_array_json, neighbor_item_wrap_json); > + } > + } > + if (neighbor_array_json) { > + json_object_put(neighbor_json, "interface", neighbor_array_json); > + } > +} > + > static void > aa_unixctl_status(struct unixctl_conn *conn, int argc OVS_UNUSED, > const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) > @@ -368,6 +892,48 @@ aa_unixctl_statistics(struct unixctl_conn *conn, int > argc OVS_UNUSED, > unixctl_command_reply(conn, ds_cstr(&ds)); > } > > +static void > +lldp_unixctl_show_neighbor(struct unixctl_conn *conn, int argc, > + const char *argv[], void *aux OVS_UNUSED) > + OVS_EXCLUDED(mutex) > +{ > + struct lldp *lldp; > + > + if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) { > + struct json *neighbor_json = json_object_create(); > + struct json *lldp_json = json_object_create(); > + > + ovs_mutex_lock(&mutex); > + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { > + if (argc > 1 && strcmp(argv[1], lldp->name)) { > + continue; > + } > + lldp_print_neighbor_json(neighbor_json, lldp); > + } > + ovs_mutex_unlock(&mutex); > + > + json_object_put(lldp_json, "lldp", neighbor_json); > + > + unixctl_command_reply_json(conn, lldp_json); > + } else { > + struct ds ds = DS_EMPTY_INITIALIZER; > + > + ds_put_format(&ds, "LLDP neighbor:\n"); > + > + ovs_mutex_lock(&mutex); > + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { > + if (argc > 1 && strcmp(argv[1], lldp->name)) { > + continue; > + } > + lldp_print_neighbor(&ds, lldp); > + } > + ovs_mutex_unlock(&mutex); > + > + unixctl_command_reply(conn, ds_cstr(&ds)); > + ds_destroy(&ds); > + } > +} > + > /* An Auto Attach mapping was configured. Populate the corresponding > * structures in the LLDP hardware. > */ > @@ -635,6 +1201,8 @@ lldp_init(void) > aa_unixctl_show_isid, NULL); > unixctl_command_register("autoattach/statistics", "[bridge]", 0, 1, > aa_unixctl_statistics, NULL); > + unixctl_command_register("lldp/neighbor", "[interface]", 0, 1, > + lldp_unixctl_show_neighbor, NULL); > } > > /* Returns true if 'lldp' should process packets from 'flow'. Sets > diff --git a/vswitchd/ovs-vswitchd.8.in b/vswitchd/ovs-vswitchd.8.in > index 98e58951d..51b0a46b5 100644 > --- a/vswitchd/ovs-vswitchd.8.in > +++ b/vswitchd/ovs-vswitchd.8.in > @@ -149,6 +149,10 @@ enabled. > Force the fault status of the CFM module on \fIinterface\fR (or all > interfaces if none is given) to be \fIstatus\fR. \fIstatus\fR can be > "true", "false", or "normal" which reverts to the standard behavior. Add another comma after “normal”, > +.IP "\fBlldp/neighbor\fR [\fIinterface\fR]" > +Displays detailed information about LLDP neighbors on \fIinterface\fR. > +If \fIinterface\fR is not specified, then it displays detailed information > +about all interfaces with LLDP enabled. > .IP "\fBstp/tcn\fR [\fIbridge\fR]" > Forces a topology change event on \fIbridge\fR if it's running STP. This > may cause it to send Topology Change Notifications to its peers and flush > -- > 2.43.5 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
