From 5b3137984d108ca8933fd7a78c5cb36ac17b04ea Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 10 Nov 2015 14:06:02 +0100 Subject: cli: add command for displaying LLDP neighbors The list of LLDP neighbors is available through the D-Bus interface and libnm already provides functions to retrieve it; make the list available through nmcli as well. Sample output: $ nmcli device lldp NEIGHBOR[0].DEVICE: eth0 NEIGHBOR[0].CHASSIS-ID: 00:13:21:58:CA:42 NEIGHBOR[0].PORT-ID: 1 NEIGHBOR[0].PORT-DESCRIPTION: 1 NEIGHBOR[0].SYSTEM-NAME: ProCurve Switch 2600-8-PWR NEIGHBOR[0].SYSTEM-DESCRIPTION: ProCurve J8762A Switch 2600-8-PWR, revision H.08.89 NEIGHBOR[0].SYSTEM-CAPABILITIES: 20 (mac-bridge,router) NEIGHBOR[1].DEVICE: eth2 NEIGHBOR[1].CHASSIS-ID: 00:01:30:F8:AD:A2 NEIGHBOR[1].PORT-ID: 1/1 NEIGHBOR[1].PORT-DESCRIPTION: Summit300-48-Port 1001 NEIGHBOR[1].SYSTEM-NAME: Summit300-48 NEIGHBOR[1].SYSTEM-DESCRIPTION: Summit300-48 - Version 7.4e.1 (Build 5) NEIGHBOR[1].SYSTEM-CAPABILITIES: 20 (mac-bridge,router) https://bugzilla.gnome.org/show_bug.cgi?id=757307 --- clients/cli/common.c | 43 ++++++++ clients/cli/common.h | 2 + clients/cli/devices.c | 231 ++++++++++++++++++++++++++++++++++++++++++- clients/cli/nmcli-completion | 16 ++- man/nmcli.1.in | 9 +- 5 files changed, 297 insertions(+), 4 deletions(-) diff --git a/clients/cli/common.c b/clients/cli/common.c index 8317764e3a..a9088ade36 100644 --- a/clients/cli/common.c +++ b/clients/cli/common.c @@ -1200,3 +1200,46 @@ nmc_rl_set_deftext (void) return 0; } +/** + * nmc_parse_lldp_capabilities: + * @value: the capabilities value + * + * Parses LLDP capabilities flags + * + * Returns: a newly allocated string containing capabilities names separated by commas. + */ +char * +nmc_parse_lldp_capabilities (guint value) +{ + /* IEEE Std 802.1AB-2009 - Table 8.4 */ + const char *names[] = { "other", "repeater", "mac-bridge", "wlan-access-point", + "router", "telephone", "docsis-cable-device", "station-only", + "c-vlan-component", "s-vlan-component", "tpmr" }; + gboolean first = TRUE; + GString *str; + int i; + + if (!value) + return g_strdup ("none"); + + str = g_string_new (""); + + for (i = 0; i < G_N_ELEMENTS (names); i++) { + if (value & (1 << i)) { + if (!first) + g_string_append_c (str, ','); + + first = FALSE; + value &= ~(1 << i); + g_string_append (str, names[i]); + } + } + + if (value) { + if (!first) + g_string_append_c (str, ','); + g_string_append (str, "reserved"); + } + + return g_string_free (str, FALSE); +} diff --git a/clients/cli/common.h b/clients/cli/common.h index c7acb653da..785b9f9bad 100644 --- a/clients/cli/common.h +++ b/clients/cli/common.h @@ -70,4 +70,6 @@ void nmc_set_in_readline (gboolean in_readline); extern char *nmc_rl_pre_input_deftext; int nmc_rl_set_deftext (void); +char *nmc_parse_lldp_capabilities (guint value); + #endif /* NMC_COMMON_H */ diff --git a/clients/cli/devices.c b/clients/cli/devices.c index 0c38419234..c295b7c648 100644 --- a/clients/cli/devices.c +++ b/clients/cli/devices.c @@ -247,6 +247,31 @@ static NmcOutputField nmc_fields_dev_show_sections[] = { #define NMC_FIELDS_DEV_SHOW_SECTIONS_COMMON "GENERAL.DEVICE,GENERAL.TYPE,GENERAL.HWADDR,GENERAL.MTU,GENERAL.STATE,"\ "GENERAL.CONNECTION,GENERAL.CON-PATH,WIRED-PROPERTIES,IP4,IP6" +/* Available fields for 'device lldp' */ +static NmcOutputField nmc_fields_dev_lldp_list[] = { + {"NAME", N_("NAME")}, /* 0 */ + {"DEVICE", N_("DEVICE")}, /* 1 */ + {"CHASSIS-ID", N_("CHASSIS-ID")}, /* 2 */ + {"PORT-ID", N_("PORT-ID")}, /* 3 */ + {"PORT-DESCRIPTION", N_("PORT-DESCRIPTION")}, /* 4 */ + {"SYSTEM-NAME", N_("SYSTEM-NAME")}, /* 5 */ + {"SYSTEM-DESCRIPTION", N_("SYSTEM-DESCRIPTION")}, /* 6 */ + {"SYSTEM-CAPABILITIES", N_("SYSTEM-CAPABILITIES")}, /* 7 */ + {"IEEE-802-1-PVID", N_("IEEE-802-1-PVID")}, /* 8 */ + {"IEEE-802-1-PPVID", N_("IEEE-802-1-PPVID")}, /* 9 */ + {"IEEE-802-1-PPVID-FLAGS", N_("IEEE-802-1-PPVID-FLAGS")}, /* 10 */ + {"IEEE-802-1-VID", N_("IEEE-802-1-VID")}, /* 11 */ + {"IEEE-802-1-VLAN-NAME", N_("IEEE-802-1-VLAN-NAME")}, /* 12 */ + {"DESTINATION", N_("DESTINATION")}, /* 13 */ + {"CHASSIS-ID-TYPE", N_("CHASSIS-ID-TYPE")}, /* 14 */ + {"PORT-ID-TYPE", N_("PORT-ID-TYPE")}, /* 15 */ + {NULL, NULL} +}; +#define NMC_FIELDS_DEV_LLDP_LIST_ALL "DEVICE,CHASSIS-ID,PORT-ID,PORT-DESCRIPTION,SYSTEM-NAME,SYSTEM-DESCRIPTION," \ + "SYSTEM-CAPABILITIES,IEEE-802-1-PVID,IEEE-802-1-PPVID,IEEE-802-1-PPVID-FLAGS," \ + "IEEE-802-1-VID,IEEE-802-1-VLAN-NAME,DESTINATION,CHASSIS-ID-TYPE,PORT-ID-TYPE" +#define NMC_FIELDS_DEV_LLDP_LIST_COMMON "DEVICE,CHASSIS-ID,PORT-ID,PORT-DESCRIPTION,SYSTEM-NAME,SYSTEM-DESCRIPTION," \ + "SYSTEM-CAPABILITIES" /* glib main loop variable - defined in nmcli.c */ extern GMainLoop *loop; @@ -257,7 +282,7 @@ static void usage (void) { g_printerr (_("Usage: nmcli device { COMMAND | help }\n\n" - "COMMAND := { status | show | connect | disconnect | delete | wifi }\n\n" + "COMMAND := { status | show | connect | disconnect | delete | wifi | lldp }\n\n" " status\n\n" " show []\n\n" " set [ifname] [autoconnect yes|no] [managed yes|no]\n\n" @@ -270,6 +295,7 @@ usage (void) " wifi hotspot [ifname ] [con-name ] [ssid ] [band a|bg] [channel ]\n\n" " [password ] [--show-password]\n\n" " wifi rescan [ifname ] [[ssid ] ...]\n\n" + " lldp [list [ifname ]]\n\n" )); } @@ -398,6 +424,17 @@ usage_device_wifi (void) "use 'nmcli device wifi list' for that.\n\n")); } +static void +usage_device_lldp (void) +{ + g_printerr (_("Usage: nmcli device lldp { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [list [ifname ]]\n" + "\n" + "List neighboring devices discovered through LLDP. The 'ifname' option can be\n" + "used to list neighbors for a particular interface.\n\n")); +} + /* quit main loop */ static void quit (void) @@ -3171,6 +3208,187 @@ do_device_wifi (NmCli *nmc, int argc, char **argv) return nmc->return_value; } +static int +show_device_lldp_list (NMDevice *device, NmCli *nmc, char *fields_str, int *counter) +{ + NmcOutputField *tmpl, *arr; + GPtrArray *neighbors; + size_t tmpl_len; + const char *str; + int i; + + neighbors = nm_device_get_lldp_neighbors (device); + + if (!neighbors || !neighbors->len) + return 0; + + tmpl = nmc_fields_dev_lldp_list; + tmpl_len = sizeof (nmc_fields_dev_lldp_list); + + /* Main header name */ + nmc->print_fields.header_name = (char *) construct_header_name (_("Device LLDP neighbors"), + nm_device_get_iface (device)); + nmc->print_fields.indices = parse_output_fields (fields_str, nmc_fields_dev_lldp_list, FALSE, NULL, NULL); + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + for (i = 0; i < neighbors->len; i++) { + NMLldpNeighbor *neighbor = neighbors->pdata[i]; + guint value; + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup_printf ("NEIGHBOR[%d]", (*counter)++)); + + set_val_strc (arr, 1, nm_device_get_iface (device)); + + if (nm_lldp_neighbor_get_attr_string_value (neighbor, NM_LLDP_ATTR_CHASSIS_ID, &str)) + set_val_strc (arr, 2, str); + + if (nm_lldp_neighbor_get_attr_string_value (neighbor, NM_LLDP_ATTR_PORT_ID, &str)) + set_val_strc (arr, 3, str); + + if (nm_lldp_neighbor_get_attr_string_value (neighbor, NM_LLDP_ATTR_PORT_DESCRIPTION, &str)) + set_val_strc (arr, 4, str); + + if (nm_lldp_neighbor_get_attr_string_value (neighbor, NM_LLDP_ATTR_SYSTEM_NAME, &str)) + set_val_strc (arr, 5, str); + + if (nm_lldp_neighbor_get_attr_string_value (neighbor, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, &str)) + set_val_strc (arr, 6, str); + + if (nm_lldp_neighbor_get_attr_uint_value (neighbor, NM_LLDP_ATTR_SYSTEM_CAPABILITIES, &value)) + set_val_str (arr, 7, g_strdup_printf ("%u (%s)", value, nmc_parse_lldp_capabilities (value))); + + if (nm_lldp_neighbor_get_attr_uint_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PVID, &value)) + set_val_str (arr, 8, g_strdup_printf ("%u", value)); + + if (nm_lldp_neighbor_get_attr_uint_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVID, &value)) + set_val_str (arr, 9, g_strdup_printf ("%u", value)); + + if (nm_lldp_neighbor_get_attr_uint_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS, &value)) + set_val_str (arr, 10, g_strdup_printf ("%u", value)); + + if (nm_lldp_neighbor_get_attr_uint_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_VID, &value)) + set_val_str (arr, 11, g_strdup_printf ("%u", value)); + + if (nm_lldp_neighbor_get_attr_string_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME, &str)) + set_val_strc (arr, 12, str); + + if (nm_lldp_neighbor_get_attr_string_value (neighbor, NM_LLDP_ATTR_DESTINATION, &str)) + set_val_strc (arr, 13, str); + + if (nm_lldp_neighbor_get_attr_uint_value (neighbor, NM_LLDP_ATTR_CHASSIS_ID_TYPE, &value)) + set_val_strc (arr, 14, g_strdup_printf ("%u", value)); + + if (nm_lldp_neighbor_get_attr_uint_value (neighbor, NM_LLDP_ATTR_PORT_ID_TYPE, &value)) + set_val_strc (arr, 15, g_strdup_printf ("%u", value)); + + g_ptr_array_add (nmc->output_data, arr); + } + + print_data (nmc); + nmc_empty_output_fields (nmc); + + return neighbors->len; +} + +static gboolean +do_device_lldp_list (NmCli *nmc, int argc, char **argv) +{ + NMDevice *device = NULL, **devices = NULL; + GError *error = NULL; + const char *ifname = NULL; + char *fields_str; + int i, counter = 0; + + while (argc > 0) { + if (strcmp (*argv, "ifname") == 0) { + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1)); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + ifname = *argv; + } else { + g_string_printf (nmc->return_text, _("Error: unknown parameter: %s"), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + argc--; + argv++; + } + + if (argc > 0) { + g_string_printf (nmc->return_text, _("Error: invalid extra argument '%s'."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + if (!nmc->required_fields || strcasecmp (nmc->required_fields, "common") == 0) + fields_str = NMC_FIELDS_DEV_LLDP_LIST_COMMON; + else if (!nmc->required_fields || strcasecmp (nmc->required_fields, "all") == 0) + fields_str = NMC_FIELDS_DEV_LLDP_LIST_ALL; + else + fields_str = nmc->required_fields; + + nmc->print_fields.indices = parse_output_fields (fields_str, nmc_fields_dev_lldp_list, FALSE, NULL, &error); + + if (error) { + g_string_printf (nmc->return_text, _("Error: 'device lldp list': %s"), error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + + devices = get_devices_sorted (nmc->client); + + if (ifname) { + for (i = 0; devices[i]; i++) { + NMDevice *candidate = devices[i]; + const char *dev_iface = nm_device_get_iface (candidate); + + if (!g_strcmp0 (dev_iface, ifname)) { + device = candidate; + break; + } + } + + if (!device) { + g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + + nmc_empty_output_fields (nmc); + show_device_lldp_list (device, nmc, fields_str, &counter); + } else { + for (i = 0; devices[i]; i++) { + nmc_empty_output_fields (nmc); + show_device_lldp_list (devices[i], nmc, fields_str, &counter); + } + } + +error: + g_free (devices); + return nmc->return_value; +} + +static NMCResultCode +do_device_lldp (NmCli *nmc, int argc, char **argv) +{ + if (argc == 0) + nmc->return_value = do_device_lldp_list (nmc, argc, argv); + else if (matches (*argv, "list") == 0) + nmc->return_value = do_device_lldp_list (nmc, argc-1, argv+1); + else { + g_string_printf (nmc->return_text, _("Error: 'device lldp' command '%s' is not valid."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + + return nmc->return_value; +} + static gboolean is_single_word (const char* line) { @@ -3332,6 +3550,17 @@ do_devices (NmCli *nmc, int argc, char **argv) goto opt_error; nmc->return_value = do_device_wifi (nmc, argc-1, argv+1); } + else if (matches (*argv, "lldp") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_device_lldp (); + goto usage_exit; + } + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) + goto opt_error; + if (!nmc->mode_specified) + nmc->multiline_output = TRUE; /* multiline mode is default for 'device lldp' */ + nmc->return_value = do_device_lldp (nmc, argc-1, argv+1); + } else { usage (); g_string_printf (nmc->return_text, _("Error: 'dev' command '%s' is not valid."), *argv); diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion index 02364c65cd..b259d46d47 100644 --- a/clients/cli/nmcli-completion +++ b/clients/cli/nmcli-completion @@ -1291,7 +1291,7 @@ _nmcli() ;; d|de|dev|devi|devic|device) if [[ ${#words[@]} -eq 2 ]]; then - _nmcli_compl_COMMAND "$command" status show connect disconnect delete wifi set + _nmcli_compl_COMMAND "$command" status show connect disconnect delete wifi set lldp elif [[ ${#words[@]} -gt 2 ]]; then case "$command" in s|st|sta|stat|statu|status) @@ -1359,7 +1359,19 @@ _nmcli() esac fi ;; - + l|ll|lld|lldp) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND "${words[2]}" list + else + case "${words[2]}" in + l|li|lis|list) + _nmcli_array_delete_at words 0 2 + OPTIONS=(ifname) + _nmcli_compl_ARGS + ;; + esac + fi + ;; esac fi ;; diff --git a/man/nmcli.1.in b/man/nmcli.1.in index 0b4d90a5bf..1881310070 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -769,7 +769,7 @@ of its latest state. .B device - show and manage network interfaces .br .TP -.SS \fICOMMAND\fP := { status | show | set | connect | disconnect | delete | wifi } +.SS \fICOMMAND\fP := { status | show | set | connect | disconnect | delete | wifi | lldp } .sp .RS .TP @@ -893,6 +893,13 @@ with hidden SSIDs. You can provide multiple \fIssid\fP parameters in order to scan more SSIDs. .br This command does not show the APs, use 'nmcli device wifi list' for that. +.TP +.B lldp [list [ifname ]] +.br +Display information about neighboring devices learned through the Link +Layer Discovery Protocol (LLDP). The \fIifname\fP option can be used to +list neighbors only for a given interface. The protocol must be +enabled in the connection settings. .RE .TP -- cgit v1.2.1