ironic-inspector/ironic_inspector/common/lldp_tlvs.py
Bob Fournier 8834927d4c Add plugin to process basic management LLDP TLVs
This adds a plugin to process the raw LLDP TLVs stored in Swift for
the Basic Mgmt, 802.1, and 802.3 data sets and store the parsed data
back in Swift.  It implements the TLV processing as described in the
specification:
http://specs.openstack.org/openstack/ironic-inspector-specs/specs/lldp-reporting.html

Change-Id: I854826787ff045ffb2807970deaba8b77cbe277d
Closes-Bug: 1647515
Related-Bug: 1626253
2017-01-31 19:51:39 -05:00

367 lines
10 KiB
Python

# 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.
""" Link Layer Discovery Protocol TLVs """
import functools
# See http://construct.readthedocs.io/en/latest/index.html
import construct
from construct import core
import netaddr
from ironic_inspector import utils
LOG = utils.getProcessingLogger(__name__)
# Constants defined according to 802.1AB-2016 LLDP spec
# https://standards.ieee.org/findstds/standard/802.1AB-2016.html
# TLV types
LLDP_TLV_END_LLDPPDU = 0
LLDP_TLV_CHASSIS_ID = 1
LLDP_TLV_PORT_ID = 2
LLDP_TLV_TTL = 3
LLDP_TLV_PORT_DESCRIPTION = 4
LLDP_TLV_SYS_NAME = 5
LLDP_TLV_SYS_DESCRIPTION = 6
LLDP_TLV_SYS_CAPABILITIES = 7
LLDP_TLV_MGMT_ADDRESS = 8
LLDP_TLV_ORG_SPECIFIC = 127
# 802.1Q defines from http://www.ieee802.org/1/pages/802.1Q-2014.html, Annex D
LLDP_802dot1_OUI = "0080c2"
# subtypes
dot1_PORT_VLANID = 1
dot1_PORT_PROTOCOL_VLANID = 2
dot1_VLAN_NAME = 3
dot1_PROTOCOL_IDENTITY = 4
dot1_MANAGEMENT_VID = 6
dot1_LINK_AGGREGATION = 7
# 802.3 defines from http://standards.ieee.org/about/get/802/802.3.html,
# section 79
LLDP_802dot3_OUI = "00120f"
# Subtypes
dot3_MACPHY_CONFIG_STATUS = 1
dot3_LINK_AGGREGATION = 3 # Deprecated, but still in use
dot3_MTU = 4
def bytes_to_int(obj):
"""Convert bytes to an integer
:param: obj - array of bytes
"""
return functools.reduce(lambda x, y: x << 8 | y, obj)
def mapping_for_enum(mapping):
"""Return tuple used for keys as a dict
:param: mapping - dict with tuple as keys
"""
return dict(mapping.keys())
def mapping_for_switch(mapping):
"""Return dict from values
:param: mapping - dict with tuple as keys
"""
return {key[0]: value for key, value in mapping.items()}
IPv4Address = core.ExprAdapter(
core.Byte[4],
encoder=lambda obj, ctx: netaddr.IPAddress(obj).words,
decoder=lambda obj, ctx: str(netaddr.IPAddress(bytes_to_int(obj)))
)
IPv6Address = core.ExprAdapter(
core.Byte[16],
encoder=lambda obj, ctx: netaddr.IPAddress(obj).words,
decoder=lambda obj, ctx: str(netaddr.IPAddress(bytes_to_int(obj)))
)
MACAddress = core.ExprAdapter(
core.Byte[6],
encoder=lambda obj, ctx: netaddr.EUI(obj).words,
decoder=lambda obj, ctx: str(netaddr.EUI(bytes_to_int(obj),
dialect=netaddr.mac_unix_expanded))
)
IANA_ADDRESS_FAMILY_ID_MAPPING = {
('ipv4', 1): IPv4Address,
('ipv6', 2): IPv6Address,
('mac', 6): MACAddress,
}
IANAAddress = core.Embedded(core.Struct(
'family' / core.Enum(core.Int8ub, **mapping_for_enum(
IANA_ADDRESS_FAMILY_ID_MAPPING)),
'value' / core.Switch(construct.this.family, mapping_for_switch(
IANA_ADDRESS_FAMILY_ID_MAPPING))))
# Note that 'GreedyString()' is used in cases where string len is not defined
CHASSIS_ID_MAPPING = {
('entPhysAlias_c', 1): core.Struct('value' / core.GreedyString("utf8")),
('ifAlias', 2): core.Struct('value' / core.GreedyString("utf8")),
('entPhysAlias_p', 3): core.Struct('value' / core.GreedyString("utf8")),
('mac_address', 4): core.Struct('value' / MACAddress),
('IANA_address', 5): IANAAddress,
('ifName', 6): core.Struct('value' / core.GreedyString("utf8")),
('local', 7): core.Struct('value' / core.GreedyString("utf8"))
}
#
# Basic Management Set TLV field definitions
#
# Chassis ID value is based on the subtype
ChassisId = core.Struct(
'subtype' / core.Enum(core.Byte, **mapping_for_enum(
CHASSIS_ID_MAPPING)),
'value' /
core.Embedded(core.Switch(construct.this.subtype,
mapping_for_switch(CHASSIS_ID_MAPPING)))
)
PORT_ID_MAPPING = {
('ifAlias', 1): core.Struct('value' / core.GreedyString("utf8")),
('entPhysicalAlias', 2): core.Struct('value' / core.GreedyString("utf8")),
('mac_address', 3): core.Struct('value' / MACAddress),
('IANA_address', 4): IANAAddress,
('ifName', 5): core.Struct('value' / core.GreedyString("utf8")),
('local', 7): core.Struct('value' / core.GreedyString("utf8"))
}
# Port ID value is based on the subtype
PortId = core.Struct(
'subtype' / core.Enum(core.Byte, **mapping_for_enum(
PORT_ID_MAPPING)),
'value' /
core.Embedded(core.Switch(construct.this.subtype,
mapping_for_switch(PORT_ID_MAPPING)))
)
PortDesc = core.Struct('value' / core.GreedyString("utf8"))
SysName = core.Struct('value' / core.GreedyString("utf8"))
SysDesc = core.Struct('value' / core.GreedyString("utf8"))
MgmtAddress = core.Struct(
'len' / core.Int8ub,
'family' / core.Enum(core.Int8ub, **mapping_for_enum(
IANA_ADDRESS_FAMILY_ID_MAPPING)),
'address' / core.Switch(construct.this.family, mapping_for_switch(
IANA_ADDRESS_FAMILY_ID_MAPPING))
)
Capabilities = core.BitStruct(
core.Padding(5),
'tpmr' / core.Bit,
'svlan' / core.Bit,
'cvlan' / core.Bit,
'station' / core.Bit,
'docsis' / core.Bit,
'telephone' / core.Bit,
'router' / core.Bit,
'wlan' / core.Bit,
'bridge' / core.Bit,
'repeater' / core.Bit,
core.Padding(1)
)
SysCapabilities = core.Struct(
'system' / Capabilities,
'enabled' / Capabilities
)
OrgSpecific = core.Struct(
'oui' / core.Bytes(3),
'subtype' / core.Int8ub
)
#
# 802.1Q TLV field definitions
# See http://www.ieee802.org/1/pages/802.1Q-2014.html, Annex D
#
Dot1_UntaggedVlanId = core.Struct('value' / core.Int16ub)
Dot1_PortProtocolVlan = core.Struct(
'flags' / core.BitStruct(
core.Padding(5),
'enabled' / core.Flag,
'supported' / core.Flag,
core.Padding(1),
),
'vlanid' / core.Int16ub
)
Dot1_VlanName = core.Struct(
'vlanid' / core.Int16ub,
'name_len' / core.Rebuild(core.Int8ub,
construct.len_(construct.this.value)),
'vlan_name' / core.String(construct.this.name_len, "utf8")
)
Dot1_ProtocolIdentity = core.Struct(
'len' / core.Rebuild(core.Int8ub, construct.len_(construct.this.value)),
'protocol' / core.Bytes(construct.this.len)
)
Dot1_MgmtVlanId = core.Struct('value' / core.Int16ub)
Dot1_LinkAggregationId = core.Struct(
'status' / core.BitStruct(
core.Padding(6),
'enabled' / core.Flag,
'supported' / core.Flag
),
'portid' / core.Int32ub
)
#
# 802.3 TLV field definitions
# See http://standards.ieee.org/about/get/802/802.3.html,
# section 79
#
def get_autoneg_cap(pmd):
"""Get autonegotiated capability strings
This returns a list of capability strings from the Physical Media
Dependent (PMD) capability bits.
:param pmd: PMD bits
:return: Sorted ist containing capability strings
"""
caps_set = set()
pmd_map = [
(pmd._10base_t_hdx, '10BASE-T hdx'),
(pmd._10base_t_hdx, '10BASE-T fdx'),
(pmd._10base_t4, '10BASE-T4'),
(pmd._100base_tx_hdx, '100BASE-TX hdx'),
(pmd._100base_tx_fdx, '100BASE-TX fdx'),
(pmd._100base_t2_hdx, '100BASE-T2 hdx'),
(pmd._100base_t2_fdx, '100BASE-T2 fdx'),
(pmd.pause_fdx, 'PAUSE fdx'),
(pmd.asym_pause, 'Asym PAUSE fdx'),
(pmd.sym_pause, 'Sym PAUSE fdx'),
(pmd.asym_sym_pause, 'Asym and Sym PAUSE fdx'),
(pmd._1000base_x_hdx, '1000BASE-X hdx'),
(pmd._1000base_x_fdx, '1000BASE-X fdx'),
(pmd._1000base_t_hdx, '1000BASE-T hdx'),
(pmd._1000base_t_fdx, '1000BASE-T fdx')]
for bit, cap in pmd_map:
if bit:
caps_set.add(cap)
return sorted(caps_set)
Dot3_MACPhy_Config_Status = core.Struct(
'autoneg' / core.BitStruct(
core.Padding(6),
'enabled' / core.Flag,
'supported' / core.Flag,
),
# See IANAifMauAutoNegCapBits
# RFC 4836, Definitions of Managed Objects for IEEE 802.3
'pmd_autoneg' / core.BitStruct(
core.Padding(1),
'_10base_t_hdx' / core.Bit,
'_10base_t_fdx' / core.Bit,
'_10base_t4' / core.Bit,
'_100base_tx_hdx' / core.Bit,
'_100base_tx_fdx' / core.Bit,
'_100base_t2_hdx' / core.Bit,
'_100base_t2_fdx' / core.Bit,
'pause_fdx' / core.Bit,
'asym_pause' / core.Bit,
'sym_pause' / core.Bit,
'asym_sym_pause' / core.Bit,
'_1000base_x_hdx' / core.Bit,
'_1000base_x_fdx' / core.Bit,
'_1000base_t_hdx' / core.Bit,
'_1000base_t_fdx' / core.Bit
),
'mau_type' / core.Int16ub
)
# See ifMauTypeList in
# RFC 4836, Definitions of Managed Objects for IEEE 802.3
OPER_MAU_TYPES = {
0: "Unknown",
1: "AUI",
2: "10BASE-5",
3: "FOIRL",
4: "10BASE-2",
5: "10BASE-T duplex mode unknown",
6: "10BASE-FP",
7: "10BASE-FB",
8: "10BASE-FL duplex mode unknown",
9: "10BROAD36",
10: "10BASE-T half duplex",
11: "10BASE-T full duplex",
12: "10BASE-FL half duplex",
13: "10BASE-FL full duplex",
14: "100 BASE-T4",
15: "100BASE-TX half duplex",
16: "100BASE-TX full duplex",
17: "100BASE-FX half duplex",
18: "100BASE-FX full duplex",
19: "100BASE-T2 half duplex",
20: "100BASE-T2 full duplex",
21: "1000BASE-X half duplex",
22: "1000BASE-X full duplex",
23: "1000BASE-LX half duplex",
24: "1000BASE-LX full duplex",
25: "1000BASE-SX half duplex",
26: "1000BASE-SX full duplex",
27: "1000BASE-CX half duplex",
28: "1000BASE-CX full duplex",
29: "1000BASE-T half duplex",
30: "1000BASE-T full duplex",
31: "10GBASE-X",
32: "10GBASE-LX4",
33: "10GBASE-R",
34: "10GBASE-ER",
35: "10GBASE-LR",
36: "10GBASE-SR",
37: "10GBASE-W",
38: "10GBASE-EW",
39: "10GBASE-LW",
40: "10GBASE-SW",
41: "10GBASE-CX4",
42: "2BASE-TL",
43: "10PASS-TS",
44: "100BASE-BX10D",
45: "100BASE-BX10U",
46: "100BASE-LX10",
47: "1000BASE-BX10D",
48: "1000BASE-BX10U",
49: "1000BASE-LX10",
50: "1000BASE-PX10D",
51: "1000BASE-PX10U",
52: "1000BASE-PX20D",
53: "1000BASE-PX20U",
}
Dot3_MTU = core.Struct('value' / core.Int16ub)