diff --git a/op-mode-definitions/lldp.xml b/op-mode-definitions/lldp.xml index 105bfe237..e954fb8cf 100644 --- a/op-mode-definitions/lldp.xml +++ b/op-mode-definitions/lldp.xml @@ -1,37 +1,37 @@ <?xml version="1.0" encoding="UTF-8"?> <interfaceDefinition> <node name="show"> <children> <node name="lldp"> <properties> <help>Show LLDP (Link Layer Discovery Protocol)</help> </properties> <children> <node name="neighbors"> <properties> <help>Show LLDP neighbors</help> </properties> <command>${vyos_op_scripts_dir}/lldp_op.py --all</command> <children> <node name="detail"> <properties> <help>Show LLDP neighbor details</help> </properties> - <command>/usr/sbin/lldpctl -f plain</command> + <command>${vyos_op_scripts_dir}/lldp_op.py --detail</command> </node> <tagNode name="interface"> <properties> <help>Show LLDP for specified interface</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> </properties> <command>${vyos_op_scripts_dir}/lldp_op.py --interface $4</command> </tagNode> </children> </node> </children> </node> </children> </node> </interfaceDefinition> diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index 5d48e3210..0df6749aa 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -1,170 +1,173 @@ #!/usr/bin/env python3 # # Copyright (C) 2019 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - import argparse import jinja2 -from xml.dom import minidom from sys import exit from tabulate import tabulate +from xml.dom import minidom -from vyos.util import popen +from vyos.util import cmd from vyos.config import Config parser = argparse.ArgumentParser() parser.add_argument("-a", "--all", action="store_true", help="Show LLDP neighbors on all interfaces") +parser.add_argument("-d", "--detail", action="store_true", help="Show detailes LLDP neighbor information on all interfaces") parser.add_argument("-i", "--interface", action="store", help="Show LLDP neighbors on specific interface") # Please be careful if you edit the template. lldp_out = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S - Station D - Docsis, T - Telephone, O - Other Device ID Local Proto Cap Platform Port ID --------- ----- ----- --- -------- ------- {% for n in neighbors -%} {{ "%-25s" | format(n.chassis) }} {{ "%-9s" | format(n.interface) }} {{ "%-6s" | format(n.proto) }} {{ "%-5s" | format(n.cap) }} {{ "%-20s" | format(n.platform) }} {{ n.port }} {% endfor -%} """ -def _get_neighbors(): - command = '/usr/sbin/lldpcli -f xml show neighbors' - out,_ = popen(command) - return out +def get_neighbors(): + return cmd('/usr/sbin/lldpcli -f xml show neighbors') def extract_neighbor(neighbor): """ Extract LLDP neighbor information from XML document passed as param neighbor <lldp> <interface label="Interface" name="eth0" via="LLDP" rid="3" age="0 day, 00:17:42"> <chassis label="Chassis"> <id label="ChassisID" type="mac">00:50:56:9d:a6:11</id> <name label="SysName">VyOS</name> <descr label="SysDescr">VyOS unknown</descr> <mgmt-ip label="MgmtIP">172.18.254.203</mgmt-ip> <mgmt-ip label="MgmtIP">fe80::250:56ff:fe9d:a611</mgmt-ip> <capability label="Capability" type="Bridge" enabled="off"/> <capability label="Capability" type="Router" enabled="on"/> <capability label="Capability" type="Wlan" enabled="off"/> <capability label="Capability" type="Station" enabled="off"/> </chassis> <port label="Port"> <id label="PortID" type="mac">00:50:56:9d:a6:11</id> <descr label="PortDescr">eth0</descr> <ttl label="TTL">120</ttl> <auto-negotiation label="PMD autoneg" supported="no" enabled="no"> <current label="MAU oper type">10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable</current> </auto-negotiation> </port> <vlan label="VLAN" vlan-id="203">eth0.203</vlan> <lldp-med label="LLDP-MED"> <device-type label="Device Type">Network Connectivity Device</device-type> <capability label="Capability" type="Capabilities" available="yes"/> <capability label="Capability" type="Policy" available="yes"/> <capability label="Capability" type="Location" available="yes"/> <capability label="Capability" type="MDI/PSE" available="yes"/> <capability label="Capability" type="MDI/PD" available="yes"/> <capability label="Capability" type="Inventory" available="yes"/> <inventory label="Inventory"> <hardware label="Hardware Revision">None</hardware> <software label="Software Revision">4.19.54-amd64-vyos</software> <firmware label="Firmware Revision">6.00</firmware> <serial label="Serial Number">VMware-42 1d cf 87 ab 7f da 7e-3</serial> <manufacturer label="Manufacturer">VMware, Inc.</manufacturer> <model label="Model">VMware Virtual Platform</model> <asset label="Asset ID">No Asset Tag</asset> </inventory> </lldp-med> </interface> </lldp> """ device = { 'interface' : neighbor.getAttribute('name'), 'chassis' : '', 'proto' : neighbor.getAttribute('via'), 'descr' : '', 'cap' : '', 'platform' : '', 'port' : '' } # first change to <chassis> node and then retrieve <name> and <descr> chassis = neighbor.getElementsByTagName('chassis') device['chassis'] = chassis[0].getElementsByTagName('name')[0].firstChild.data # Cisco IOS comes with a ',' remove character .... device['platform'] = chassis[0].getElementsByTagName('descr')[0].firstChild.data[:20].replace(',',' ') # extract capabilities for capability in chassis[0].getElementsByTagName('capability'): # we are only interested in enabled capabilities ... if capability.getAttribute('enabled') == "on": if capability.getAttribute('type') == "Router": device['cap'] += 'R' elif capability.getAttribute('type') == "Bridge": device['cap'] += 'B' elif capability.getAttribute('type') == "Wlan": device['cap'] += 'W' elif capability.getAttribute('type') == "Station": device['cap'] += 'S' elif capability.getAttribute('type') == "Repeater": device['cap'] += 'r' elif capability.getAttribute('type') == "Telephone": device['cap'] += 'T' elif capability.getAttribute('type') == "Docsis": device['cap'] += 'D' elif capability.getAttribute('type') == "Other": device['cap'] += 'O' # first change to <port> node and then retrieve <descr> port = neighbor.getElementsByTagName('port') port = port[0].getElementsByTagName('descr')[0].firstChild.data device['port'] = port return device if __name__ == '__main__': args = parser.parse_args() tmp = { 'neighbors' : [] } c = Config() if not c.exists_effective(['service', 'lldp']): print('Service LLDP is not configured') exit(0) if args.all: - neighbors = minidom.parseString(_get_neighbors()) + neighbors = minidom.parseString(get_neighbors()) for neighbor in neighbors.getElementsByTagName('interface'): tmp['neighbors'].append( extract_neighbor(neighbor) ) + elif args.detail: + out = cmd('/usr/sbin/lldpctl -f plain') + print(out) + exit(0) + elif args.interface: - neighbors = minidom.parseString(_get_neighbors()) + neighbors = minidom.parseString(get_neighbors()) for neighbor in neighbors.getElementsByTagName('interface'): # check if neighbor appeared on proper interface if neighbor.getAttribute('name') == args.interface: tmp['neighbors'].append( extract_neighbor(neighbor) ) else: parser.print_help() exit(1) tmpl = jinja2.Template(lldp_out) config_text = tmpl.render(tmp) print(config_text) exit(0)