Source code for ironic_inspector.common.lldp_parsers

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

""" Names and mapping functions used to map LLDP TLVs to name/value pairs """

import binascii

from construct import core
import netaddr

from ironic_inspector.common.i18n import _, _LW
from ironic_inspector.common import lldp_tlvs as tlv
from ironic_inspector import utils

LOG = utils.getProcessingLogger(__name__)


# Names used in name/value pair from parsed TLVs
LLDP_CHASSIS_ID_NM = 'switch_chassis_id'
LLDP_PORT_ID_NM = 'switch_port_id'
LLDP_PORT_DESC_NM = 'switch_port_description'
LLDP_SYS_NAME_NM = 'switch_system_name'
LLDP_SYS_DESC_NM = 'switch_system_description'
LLDP_SWITCH_CAP_NM = 'switch_capabilities'
LLDP_CAP_SUPPORT_NM = 'switch_capabilities_support'
LLDP_CAP_ENABLED_NM = 'switch_capabilities_enabled'
LLDP_MGMT_ADDRESSES_NM = 'switch_mgmt_addresses'
LLDP_PORT_VLANID_NM = 'switch_port_untagged_vlan_id'
LLDP_PORT_PROT_NM = 'switch_port_protocol'
LLDP_PORT_PROT_VLAN_ENABLED_NM = 'switch_port_protocol_vlan_enabled'
LLDP_PORT_PROT_VLAN_SUPPORT_NM = 'switch_port_protocol_vlan_support'
LLDP_PORT_PROT_VLANIDS_NM = 'switch_port_protocol_vlan_ids'
LLDP_PORT_VLANS_NM = 'switch_port_vlans'
LLDP_PROTOCOL_IDENTITIES_NM = 'switch_protocol_identities'
LLDP_PORT_MGMT_VLANID_NM = 'switch_port_management_vlan_id'
LLDP_PORT_LINK_AGG_NM = 'switch_port_link_aggregation'
LLDP_PORT_LINK_AGG_ENABLED_NM = 'switch_port_link_aggregation_enabled'
LLDP_PORT_LINK_AGG_SUPPORT_NM = 'switch_port_link_aggregation_support'
LLDP_PORT_LINK_AGG_ID_NM = 'switch_port_link_aggregation_id'
LLDP_PORT_MAC_PHY_NM = 'switch_port_mac_phy_config'
LLDP_PORT_LINK_AUTONEG_ENABLED_NM = 'switch_port_autonegotiation_enabled'
LLDP_PORT_LINK_AUTONEG_SUPPORT_NM = 'switch_port_autonegotiation_support'
LLDP_PORT_CAPABILITIES_NM = 'switch_port_physical_capabilities'
LLDP_PORT_MAU_TYPE_NM = 'switch_port_mau_type'
LLDP_MTU_NM = 'switch_port_mtu'


[docs]class LLDPParser(object): """Base class to handle parsing of LLDP TLVs""" def __init__(self, node_info, nv=None): """Create LLDPParser :param node_info - node being introspected :param nv - dictionary of name/value pairs to use """ if not nv: self.nv_dict = {} else: self.nv_dict = nv self.node_info = node_info # Parser maps are used to associate a LLDP TLV with a function handler # and arguments necessary to parse the TLV and generate one or more # name/value pairs. Each LLDP TLV maps to a tuple with the values: # function - handler function to generate name/value pairs # construct - name of construct definition for TLV # name - user-friendly name of TLV. For TLVs that generate only # one name/value pair this is the name used # len_check - boolean that indicates whether a len check # should be done on the construct # # Its valid to have a function handler of None, this is for TLVs that # are not mapped to a name/value pair (e.g. LLDP_TLV_TTL). # # Each class that inherits from this base class must provide a # parser map. self.parser_map = {}
[docs] def set_value(self, name, value): """Set name value pair in dictionary""" self.nv_dict.setdefault(name, value) # don't change key if it exists
[docs] def append_value(self, name, value): """Add value to a list mapped to name""" self.nv_dict.setdefault(name, []).append(value)
[docs] def add_single_value(self, struct, name, data): """Add a single name/value pair the the nv dict""" self.set_value(name, struct.value)
[docs] def parse_tlv(self, tlv_type, data): """Parse TLVs from mapping table :param: tlv_type - type identifier for TLV :param: data - raw TLV value """ # The handler function will generate name/value pairs using the # tlv construct definition. If the function does not exist, then no # name/value pairs will be added, but since the TLV was handled, # True will be returned s = self.parser_map.get(tlv_type) if s: func = s[0] # handler if func: try: tlv_parser = s[1] name = s[2] check_len = s[3] except KeyError as e: LOG.warning(_LW("Key error in TLV table: %s"), e, node_info=self.node_info) return False # Some constructs require a length validation to ensure the # proper number of bytes has been provided, for example # when a BitStruct is used. if check_len and (tlv_parser.sizeof() != len(data)): LOG.warning(_LW('Invalid data for %(name)s ' 'expected len %(expect)d, got %(actual)d'), {'name': name, 'expect': tlv_parser.sizeof(), 'actual': len(data)}) return False # Use the construct parser to parse TLV so that it's # individual fields can be accessed try: struct = tlv_parser.parse(data) except (core.RangeError, core.FieldError, core.MappingError, netaddr.AddrFormatError) as e: LOG.warning(_LW("TLV parse error: %s"), e, node_info=self.node_info) return False # Call functions with parsed structure try: func(struct, name, data) except ValueError as e: LOG.warning(_LW("TLV value error: %s"), e, node_info=self.node_info) return True return False # This method is in base class since it can be used by both dot1 and dot3
[docs]class LLDPBasicMgmtParser(LLDPParser): """Class to handle parsing of 802.1AB Basic Management set This class will also handle 802.1Q and 802.3 OUI TLVs """ def __init__(self, nv=None): super(LLDPBasicMgmtParser, self).__init__(nv) self.parser_map = { tlv.LLDP_TLV_CHASSIS_ID: (self.add_single_value, tlv.ChassisId, LLDP_CHASSIS_ID_NM, False), tlv.LLDP_TLV_PORT_ID: (self.add_single_value, tlv.PortId, LLDP_PORT_ID_NM, False), tlv.LLDP_TLV_TTL: (None, None, None, False), tlv.LLDP_TLV_PORT_DESCRIPTION: (self.add_single_value, tlv.PortDesc, LLDP_PORT_DESC_NM, False), tlv.LLDP_TLV_SYS_NAME: (self.add_single_value, tlv.SysName, LLDP_SYS_NAME_NM, False), tlv.LLDP_TLV_SYS_DESCRIPTION: (self.add_single_value, tlv.SysDesc, LLDP_SYS_DESC_NM, False), tlv.LLDP_TLV_SYS_CAPABILITIES: (self.add_capabilities, tlv.SysCapabilities, LLDP_SWITCH_CAP_NM, True), tlv.LLDP_TLV_MGMT_ADDRESS: (self.add_mgmt_address, tlv.MgmtAddress, LLDP_MGMT_ADDRESSES_NM, False), tlv.LLDP_TLV_ORG_SPECIFIC: (self.handle_org_specific_tlv, tlv.OrgSpecific, None, False), tlv.LLDP_TLV_END_LLDPPDU: (None, None, None, False) }
[docs] def add_mgmt_address(self, struct, name, data): """Handle LLDP_TLV_MGMT_ADDRESS""" # There may be multiple Mgmt Address TLVs so store in list self.append_value(name, struct.address)
def _get_capabilities_list(self, caps): """Get capabilities from bit map""" cap_map = [ (caps.repeater, 'Repeater'), (caps.bridge, 'Bridge'), (caps.wlan, 'WLAN'), (caps.router, 'Router'), (caps.telephone, 'Telephone'), (caps.docsis, 'DOCSIS cable device'), (caps.station, 'Station only'), (caps.cvlan, 'C-Vlan'), (caps.svlan, 'S-Vlan'), (caps.tpmr, 'TPMR')] return [cap for (bit, cap) in cap_map if bit]
[docs] def add_capabilities(self, struct, name, data): """Handle LLDP_TLV_SYS_CAPABILITIES""" self.set_value(LLDP_CAP_SUPPORT_NM, self._get_capabilities_list(struct.system)) self.set_value(LLDP_CAP_ENABLED_NM, self._get_capabilities_list(struct.enabled))
[docs] def handle_org_specific_tlv(self, struct, name, data): """Handle Organizationally Unique ID TLVs This class supports 802.1Q and 802.3 OUI TLVs See http://www.ieee802.org/1/pages/802.1Q-2014.html, Annex D and http: // standards.ieee.org / about / get / 802 / 802.3.html """ oui = binascii.hexlify(struct.oui).decode() subtype = struct.subtype oui_data = data[4:] if oui == tlv.LLDP_802dot1_OUI: parser = LLDPdot1Parser(self.node_info, self.nv_dict) if parser.parse_tlv(subtype, oui_data): LOG.debug("Handled 802.1 subtype %d", subtype) else: LOG.debug("Subtype %d not found for 802.1", subtype) elif oui == tlv.LLDP_802dot3_OUI: parser = LLDPdot3Parser(self.node_info, self.nv_dict) if parser.parse_tlv(subtype, oui_data): LOG.debug("Handled 802.3 subtype %d", subtype) else: LOG.debug("Subtype %d not found for 802.3", subtype) else: LOG.debug("Organizationally Unique ID %s not " "recognized", oui)
[docs]class LLDPdot1Parser(LLDPParser): """Class to handle parsing of 802.1Q TLVs""" def __init__(self, node_info, nv=None): super(LLDPdot1Parser, self).__init__(node_info, nv) self.parser_map = { tlv.dot1_PORT_VLANID: (self.add_single_value, tlv.Dot1_UntaggedVlanId, LLDP_PORT_VLANID_NM, False), tlv.dot1_PORT_PROTOCOL_VLANID: (self.add_dot1_port_protocol_vlan, tlv.Dot1_PortProtocolVlan, LLDP_PORT_PROT_NM, True), tlv.dot1_VLAN_NAME: (self.add_dot1_vlans, tlv.Dot1_VlanName, None, False), tlv.dot1_PROTOCOL_IDENTITY: (self.add_dot1_protocol_identities, tlv.Dot1_ProtocolIdentity, LLDP_PROTOCOL_IDENTITIES_NM, False), tlv.dot1_MANAGEMENT_VID: (self.add_single_value, tlv.Dot1_MgmtVlanId, LLDP_PORT_MGMT_VLANID_NM, False), tlv.dot1_LINK_AGGREGATION: (self.add_dot1_link_aggregation, tlv.Dot1_LinkAggregationId, LLDP_PORT_LINK_AGG_NM, True) }
[docs] def add_dot1_port_protocol_vlan(self, struct, name, data): """Handle dot1_PORT_PROTOCOL_VLANID""" self.set_value(LLDP_PORT_PROT_VLAN_ENABLED_NM, struct.flags.enabled) self.set_value(LLDP_PORT_PROT_VLAN_SUPPORT_NM, struct.flags.supported) # There can be multiple port/protocol vlans TLVs, store in list self.append_value(LLDP_PORT_PROT_VLANIDS_NM, struct.vlanid)
[docs] def add_dot1_vlans(self, struct, name, data): """Handle dot1_VLAN_NAME""" # There can be multiple vlan TLVs, add dictionary entry with id/vlan vlan_dict = {} vlan_dict['name'] = struct.vlan_name vlan_dict['id'] = struct.vlanid self.append_value(LLDP_PORT_VLANS_NM, vlan_dict)
[docs] def add_dot1_protocol_identities(self, struct, name, data): """handle dot1_PROTOCOL_IDENTITY""" # There can be multiple protocol ids TLVs, store in list self.append_value(LLDP_PROTOCOL_IDENTITIES_NM, binascii.b2a_hex(struct.protocol).decode())
[docs]class LLDPdot3Parser(LLDPParser): """Class to handle parsing of 802.3 TLVs""" def __init__(self, node_info, nv=None): super(LLDPdot3Parser, self).__init__(node_info, nv) # Note that 802.3 link Aggregation has been deprecated and moved to # 802.1 spec, but it is in the same format. Use the same function as # dot1 handler. self.parser_map = { tlv.dot3_MACPHY_CONFIG_STATUS: (self.add_dot3_macphy_config, tlv.Dot3_MACPhy_Config_Status, LLDP_PORT_MAC_PHY_NM, True), tlv.dot3_LINK_AGGREGATION: (self.add_dot1_link_aggregation, tlv.Dot1_LinkAggregationId, LLDP_PORT_LINK_AGG_NM, True), tlv.dot3_MTU: (self.add_single_value, tlv.Dot3_MTU, LLDP_MTU_NM, False) }
[docs] def add_dot3_macphy_config(self, struct, name, data): """Handle dot3_MACPHY_CONFIG_STATUS""" try: mau_type = tlv.OPER_MAU_TYPES[struct.mau_type] except KeyError: raise ValueError(_('Invalid index for mau type')) self.set_value(LLDP_PORT_LINK_AUTONEG_ENABLED_NM, struct.autoneg.enabled) self.set_value(LLDP_PORT_LINK_AUTONEG_SUPPORT_NM, struct.autoneg.supported) self.set_value(LLDP_PORT_CAPABILITIES_NM, tlv.get_autoneg_cap(struct.pmd_autoneg)) self.set_value(LLDP_PORT_MAU_TYPE_NM, mau_type)