Changliang Wu <changliang...@smartx.com> writes: > New appctl 'lldp/neighbor' displays lldp neighbor information. > Support json output with --format json --pretty > > Signed-off-by: Changliang Wu <changliang...@smartx.com> > --- > NEWS | 1 + > lib/ovs-lldp.c | 356 +++++++++++++++++++++++++++++++++++++ > vswitchd/ovs-vswitchd.8.in | 4 + > 3 files changed, 361 insertions(+) > > diff --git a/NEWS b/NEWS > index c7bb53f2d..931fe5c71 100644 > --- a/NEWS > +++ b/NEWS > @@ -20,6 +20,7 @@ Post-v3.5.0 > core file size. > - ovs-appctl: > * Added JSON output support to the 'ovs/route/show' command. > + * Added 'lldp/neighbor' command that displays lldp neighbor infomation. > - SSL/TLS: > * Support for deprecated TLSv1 and TLSv1.1 protocols on OpenFlow and > database connections is now removed. > diff --git a/lib/ovs-lldp.c b/lib/ovs-lldp.c > index 152777248..330cb16ba 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" > @@ -310,6 +313,315 @@ aa_print_isid_status(struct ds *ds, struct lldp *lldp) > OVS_REQUIRES(mutex) > } > } > > +static void > +lldp_print_neighbor(struct ds *ds, struct lldp *lldp) OVS_REQUIRES(mutex) > +{ > + struct lldpd_hardware *hw; > + struct lldpd_port *port; > + const char *none_str = "<None>";
We try to keep reverse xmas tree arguments where possible. It isn't a hard rule in the coding style, but it can make the function easier to read. Please reorder. > + if (!lldp->lldpd) { > + return; > + } > + > + bool is_first_interface = true; > + LIST_FOR_EACH (hw, h_entries, &lldp->lldpd->g_hardware) { > + if (!hw->h_rports.next) { > + continue; > + } > + LIST_FOR_EACH (port, p_entries, &hw->h_rports) { > + if (!port->p_chassis) { > + continue; > + } > + if (is_first_interface) { > + is_first_interface = false; > + } else { > + ds_put_format(ds, "\n"); > + } > + ds_put_format(ds, "Interface: %s\n", lldp->name); > + ds_put_format(ds, " Chassis:\n"); > + > + struct ds id = DS_EMPTY_INITIALIZER; Please call this 'chassis_id'. > + 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_format(ds, " %-20s%s\n", "Chassis ID:", > + id.length ? ds_cstr_ro(&id) : none_str); > + ds_put_format(ds, " %-20s%s\n", "SysName:", > + strlen(port->p_chassis->c_name) > + ? port->p_chassis->c_name > + : none_str); > + ds_put_format(ds, " %-20s%s\n", "SysDescr:", > + strlen(port->p_chassis->c_descr) > + ? port->p_chassis->c_descr > + : none_str); > + ds_destroy(&id); > + > + 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; Please don't shrink this case onto one line. > + > + default: continue; Please don't shrink this case onto one line. > + } > + 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); > + } > + > + 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"); > + } > + 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:", > + 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"); > + } I understand that this is how it is printed from lldpd, but this output really doesn't seem pleasant. LLDPD does print this way, however: ... Capability: Bridge, off Capability: Router, off Capability: Wlan, off ... So I guess it would be fine to keep as is. > + > + ds_put_format(ds, " Port:\n"); > + > + if (port->p_id_subtype == LLDP_PORTID_SUBTYPE_LLADDR) { > + ds_init(&id); Please create a separate lladdr dynamic string. > + if (port->p_id_len > 0) { > + ds_put_hex_with_delimiter(&id, (uint8_t *) port->p_id, > + port->p_id_len, ":"); > + } > + ds_put_format(ds, " %-20s%s\n", "PortID:", > + id.length ? ds_cstr_ro(&id) : none_str); > + ds_destroy(&id); > + } else { > + ds_put_format(ds, " %-20s%.*s\n", "PortID:", > + (int) (port->p_id ? port->p_id_len > + : strlen(none_str)), > + port->p_id ?: none_str); > + } > + ds_put_format(ds, " %-20s%s\n", "PortDescr:", > + strlen(port->p_descr) ? port->p_descr : none_str); > + ds_put_format(ds, " %-20s%d\n", "TTL:", > port->p_chassis->c_ttl); > + } > + } > +} > + > +static void > +lldp_print_neighbor_json(struct json *interface_json, struct lldp *lldp) > + OVS_REQUIRES(mutex) > +{ > + struct lldpd_hardware *hw; > + struct lldpd_port *port; > + const char *none_str = "<None>"; Same comment. > + if (!lldp->lldpd) { > + return; > + } > + > + struct json *interface_item_warp_json_last = NULL; > + struct json *interface_array_json = NULL; > + bool has_multi_interfaces = false; > + > + LIST_FOR_EACH (hw, h_entries, &lldp->lldpd->g_hardware) { > + if (!hw->h_rports.next) { > + continue; > + } > + LIST_FOR_EACH (port, p_entries, &hw->h_rports) { > + if (!port->p_chassis) { > + continue; > + } > + > + struct json *interface_item_warp_json = json_object_create(); > + struct json *interface_item_json = json_object_create(); > + struct json *chassis_json = json_object_create(); > + struct json *chassis_sys_json = json_object_create(); > + struct json *chassis_id_json = json_object_create(); > + struct json *chassis_mgmt_ip_json = json_array_create_empty(); > + struct json *chassis_mgmt_iface_json = json_array_create_empty(); > + struct json *chassis_capability_json = json_array_create_empty(); > + struct json *chassis_capability_item_json; > + struct json *port_json = json_object_create(); > + struct json *port_id_json = json_object_create(); > + > + struct ds id = DS_EMPTY_INITIALIZER; This 'id' object's declaration could come before the 'if' above. > + 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, ":"); > + } > + > + json_object_put(chassis_id_json, "type", > + json_string_create("mac")); > + json_object_put(chassis_id_json, "value", > + json_string_create(id.length ? ds_cstr_ro(&id) > + : none_str)); > + json_object_put(chassis_sys_json, "id", chassis_id_json); > + > + json_object_put(chassis_json, > + strlen(port->p_chassis->c_name) > + ? port->p_chassis->c_name > + : none_str, > + chassis_sys_json); > + > + json_object_put(chassis_sys_json, "descr", > + > json_string_create(strlen(port->p_chassis->c_descr) > + ? port->p_chassis->c_descr > + : none_str)); > + > + ds_destroy(&id); > + > + struct lldpd_mgmt *mgmt; > + struct in6_addr ip; > + char addr_str[INET6_ADDRSTRLEN]; 'addr_str' and 'ip' can be reduced in scope here (inside the LIST_FOR_EACH below). 'mgmt' can be declared at the upper 'LIST_FOR_EACH (port...)'. You did this in the previous block, I think. > + LIST_FOR_EACH (mgmt, m_entries, &port->p_chassis->c_mgmt) { > + 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; As with the other block, please don't shrink these onto one line. > + } > + > + 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); > + > + 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); > + } > + json_object_put(chassis_sys_json, "capability", > + chassis_capability_json); > + > + if (port->p_id_subtype == LLDP_PORTID_SUBTYPE_LLADDR) { > + ds_init(&id); Please create a separate lladdr dynamic string. > + if (port->p_id_len > 0) { > + ds_put_hex_with_delimiter(&id, port->p_id, > port->p_id_len, > + ":"); > + } > + json_object_put(port_id_json, "type", > + json_string_create("mac")); > + json_object_put(port_id_json, "value", > + json_string_create(id.length ? > ds_cstr_ro(&id) > + : none_str)); > + > + ds_destroy(&id); > + } else { > + json_object_put(port_id_json, "type", > + json_string_create("ifname")); > + if (port->p_id) { > + struct ds port_ds = DS_EMPTY_INITIALIZER; > + ds_put_buffer(&port_ds, port->p_id, port->p_id_len); > + json_object_put(port_id_json, "value", > + > json_string_create(ds_cstr_ro(&port_ds))); > + ds_destroy(&port_ds); > + } else { > + json_object_put(port_id_json, "value", > + json_string_create(none_str)); > + } > + } > + json_object_put(port_json, "id", port_id_json); > + > + json_object_put(port_json, "desc", > + json_string_create(strlen(port->p_descr) > + ? port->p_descr > + : none_str)); > + json_object_put(port_json, "ttl", > + json_integer_create(port->p_chassis->c_ttl)); > + > + json_object_put(interface_item_json, "chassis", chassis_json); > + json_object_put(interface_item_json, "port", port_json); > + > + json_object_put(interface_item_warp_json, lldp->name, > + interface_item_json); > + > + if (has_multi_interfaces) { > + if (!interface_array_json) { > + interface_array_json = json_array_create_empty(); > + json_array_add(interface_array_json, > + interface_item_warp_json_last); > + } > + json_array_add(interface_array_json, > interface_item_warp_json); > + } else { > + has_multi_interfaces = true; > + interface_item_warp_json_last = interface_item_warp_json; > + } > + } > + } > + if (interface_array_json || interface_item_warp_json_last) { > + json_object_put(interface_json, "interface", > + interface_array_json == NULL > + ? interface_item_warp_json_last > + : interface_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 +680,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 *lldp_json = json_object_create(); > + struct json *interface_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(interface_json, lldp); > + } > + ovs_mutex_unlock(&mutex); > + > + json_object_put(lldp_json, "lldp", interface_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 +989,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. > +.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 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev