8834927d4c
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
367 lines
10 KiB
Python
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)
|