diff --git a/scapy/contrib/lldp.py b/scapy/contrib/lldp.py new file mode 100644 index 0000000000000000000000000000000000000000..8d5a4b36a99cb8354ec2ab93715c36d74137063a --- /dev/null +++ b/scapy/contrib/lldp.py @@ -0,0 +1,716 @@ +#! /usr/bin/env python +# +# scapy.contrib.description = LLDP +# scapy.contrib.status = loads + +""" + LLDP - Link Layer Discovery Protocol + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :author: Thomas Tannhaeuser, hecke@naberius.de + :license: GPLv2 + + This module is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This module 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. + + :description: + + This module provides Scapy layers for the LLDP protocol. + + normative references: + - IEEE 802.1AB 2016 - LLDP protocol, topology and MIB description + + :TODO: + - organization specific TLV e.g. ProfiNet + + :NOTES: + - you can find the layer configuration options at the end of this file + - default configuration enforces standard conform + - frame structure + (ChassisIDTLV/PortIDTLV/TimeToLiveTLV/.../EndofLLDPDUTLV) + - multiplicity of TLVs (if given by the standard) + - min sizes of strings used by the TLVs + - conf.contribs['LLDP'].strict_mode_disable() -> disable strict mode + - strict mode = True => conf.debug_dissector = True + +""" +from scapy.config import conf +from scapy.layers.dot11 import Packet +from scapy.layers.l2 import Ether, Dot1Q, bind_layers, \ + struct, BitField, StrLenField, ByteEnumField, BitEnumField, \ + BitFieldLenField, ShortField, Padding, Scapy_Exception, \ + XStrLenField +from scapy.modules.six.moves import range +from scapy.data import ETHER_TYPES + +LLDP_NEAREST_BRIDGE_MAC = '01:80:c2:00:00:0e' +LLDP_NEAREST_NON_TPMR_BRIDGE_MAC = '01:80:c2:00:00:03' +LLDP_NEAREST_CUSTOMER_BRIDGE_MAC = '01:80:c2:00:00:00' + +LLDP_ETHER_TYPE = 0x88cc +ETHER_TYPES['LLDP'] = LLDP_ETHER_TYPE + + +class LLDPInvalidFrameStructure(Scapy_Exception): + """ + basic frame structure not standard conform + (missing TLV, invalid order or multiplicity) + """ + pass + + +class LLDPInvalidLastLayerException(Scapy_Exception): + """ + in strict mode, last layer in frame must be of type LLDPDUEndOfLLDPDU + """ + pass + + +class LLDPMissingLowerLayer(Scapy_Exception): + """ + first layer below first LLDPDU must be Ethernet or Dot1q + """ + pass + + +class LLDPInvalidTLVCount(Scapy_Exception): + """ + invalid number of entries for a specific TLV type + """ + pass + + +class LLDPInvalidLengthField(Scapy_Exception): + """ + invalid value of length field + """ + pass + + +class LLDPDU(Packet): + """ + base class for all LLDP data units + """ + TYPES = { + 0x00: 'end of LLDPDU', + 0x01: 'chassis id', + 0x02: 'port id', + 0x03: 'time to live', + 0x04: 'port description', + 0x05: 'system name', + 0x06: 'system description', + 0x07: 'system capabilities', + 0x08: 'management address', + range(0x09, 0x7e): 'reserved - future standardization', + 127: 'organisation specific TLV' + } + + DOT1Q_HEADER_LEN = 4 + ETHER_HEADER_LEN = 14 + ETHER_FSC_LEN = 4 + ETHER_FRAME_MIN_LEN = 64 + + LAYER_STACK = [] + LAYER_MULTIPLICITIES = {} + + def guess_payload_class(self, payload): + # type is a 7-bit bitfield spanning bits 1..7 -> div 2 + lldpdu_tlv_type = struct.unpack("b", payload[0])[0] / 2 + return LLDPDU_CLASS_TYPES[lldpdu_tlv_type] + + @staticmethod + def _dot1q_headers_size(layer): + """ + calculate size of lower dot1q layers (if present) + :param layer: the layer to start at + :return: size of vlan headers, layer below lowest vlan header + """ + + vlan_headers_size = 0 + under_layer = layer + + while under_layer and isinstance(under_layer, Dot1Q): + vlan_headers_size += LLDPDU.DOT1Q_HEADER_LEN + under_layer = under_layer.underlayer + + return vlan_headers_size, under_layer + + def post_build(self, pkt, pay): + + last_layer = not len(pay) + if last_layer and conf.contribs['LLDP'].strict_mode() and \ + type(self).__name__ != LLDPDUEndOfLLDPDU.__name__: + raise LLDPInvalidLastLayerException('Last layer must be instance ' + 'of LLDPDUEndOfLLDPDU - ' + 'got {}'. + format(type(self).__name__)) + + under_layer = self.underlayer + + if under_layer is None: + if conf.contribs['LLDP'].strict_mode(): + raise LLDPMissingLowerLayer('No lower layer (Ethernet ' + 'or Dot1Q) provided.') + else: + return pkt + pay + + not_lowest_lldpdu = under_layer.__class__.__base__.__name__ is 'LLDPDU' + if not_lowest_lldpdu: + return pkt + pay + + frame_size, under_layer = LLDPDU._dot1q_headers_size(under_layer) + + if not under_layer or not isinstance(under_layer, Ether): + if conf.contribs['LLDP'].strict_mode(): + raise LLDPMissingLowerLayer('No Ethernet layer provided.') + else: + return pkt + pay + + frame_size += LLDPDU.ETHER_HEADER_LEN + frame_size += len(pkt) + len(pay) + LLDPDU.ETHER_FSC_LEN + if frame_size < LLDPDU.ETHER_FRAME_MIN_LEN: + pad = Padding() + pad.load = b'\x00' * (LLDPDU.ETHER_FRAME_MIN_LEN - frame_size) + return pkt + pay + str(pad) + else: + return pkt + pay + + @staticmethod + def _frame_structure_check(structure_description): + """ + check if the structure of the frame is conform to the basic + frame structure defined by the standard + :param structure_description: string-list reflecting LLDP-msg structure + """ + + standard_frame_structure = [LLDPDUChassisID.__name__, + LLDPDUPortID.__name__, + LLDPDUTimeToLive.__name__, + '<...>', + LLDPDUEndOfLLDPDU.__name__] + + if len(structure_description) < 4: + raise LLDPInvalidFrameStructure( + 'Invalid frame structure.\ngot: {}\nexpected: ' + '{}'.format(' '.join(structure_description), + ' '.join(standard_frame_structure))) + + for idx, layer_name in enumerate(standard_frame_structure): + + if layer_name == '<...>': + break + if layer_name != structure_description[idx]: + raise LLDPInvalidFrameStructure( + 'Invalid frame structure.\ngot: {}\nexpected: ' + '{}'.format(' '.join(structure_description), + ' '.join(standard_frame_structure))) + + if structure_description[-1] != standard_frame_structure[-1]: + raise LLDPInvalidFrameStructure( + 'Invalid frame structure.\ngot: {}\nexpected: ' + '{}'.format(' '.join(structure_description), + ' '.join(standard_frame_structure))) + + @staticmethod + def _tlv_multiplicities_check(tlv_type_count): + """ + check if multiplicity of present TLVs conforms to the standard + :param tlv_type_count: dict containing counte-per-TLV + """ + + # * : 0..n, 1 : one and only one. + standard_multiplicities = { + LLDPDUEndOfLLDPDU.__name__: 1, + LLDPDUChassisID.__name__: 1, + LLDPDUPortID.__name__: 1, + LLDPDUTimeToLive.__name__: 1, + LLDPDUPortDescription: '*', + LLDPDUSystemName: '*', + LLDPDUSystemDescription: '*', + LLDPDUSystemCapabilities: '*', + LLDPDUManagementAddress: '*' + } + + for tlv_type_name in standard_multiplicities: + + standard_tlv_multiplicity = \ + standard_multiplicities[tlv_type_name] + if standard_tlv_multiplicity == '*': + continue + + try: + if tlv_type_count[tlv_type_name] != standard_tlv_multiplicity: + raise LLDPInvalidTLVCount( + 'Invalid number of entries for TLV type ' + '{} - expected {} entries, got ' + '{}'.format(tlv_type_name, + standard_tlv_multiplicity, + tlv_type_count[tlv_type_name])) + + except KeyError as err: + raise LLDPInvalidTLVCount('Missing TLV layer of type ' + '{}.'.format(tlv_type_name)) + + def pre_dissect(self, s): + + if conf.contribs['LLDP'].strict_mode(): + if self.__class__.__name__ == 'LLDPDU': + LLDPDU.LAYER_STACK = [] + LLDPDU.LAYER_MULTIPLICITIES = {} + else: + LLDPDU.LAYER_STACK.append(self.__class__.__name__) + try: + LLDPDU.LAYER_MULTIPLICITIES[self.__class__.__name__] += 1 + except KeyError as err: + LLDPDU.LAYER_MULTIPLICITIES[self.__class__.__name__] = 1 + + return s + + def dissection_done(self, pkt): + + if self.__class__.__name__ == 'LLDPDU' and \ + conf.contribs['LLDP'].strict_mode(): + LLDPDU._frame_structure_check(LLDPDU.LAYER_STACK) + LLDPDU._tlv_multiplicities_check(LLDPDU.LAYER_MULTIPLICITIES) + + super(LLDPDU, self).dissection_done(pkt) + + +class LLDPDUChassisID(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.2 / p. 26 + """ + LLDP_CHASSIS_ID_TLV_SUBTYPES = { + 0x00: 'reserved', + 0x01: 'chassis component', + 0x02: 'interface alias', + 0x03: 'port component', + 0x04: 'MAC address', + 0x05: 'network address', + 0x06: 'interface name', + 0x07: 'locally assigned', + range(0x08, 0xff): 'reserved' + } + + SUBTYPE_RESERVED = 0x00 + SUBTYPE_CHASSIS_COMPONENT = 0x01 + SUBTYPE_INTERFACE_ALIAS = 0x02 + SUBTYPE_PORT_COMPONENT = 0x03 + SUBTYPE_MAC_ADDRESS = 0x04 + SUBTYPE_NETWORK_ADDRESS = 0x05 + SUBTYPE_INTERFACE_NAME = 0x06 + SUBTYPE_LOCALLY_ASSIGNED = 0x07 + + fields_desc = [ + BitEnumField('_type', 0x01, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='id', + adjust=lambda pkt, x: len(pkt.id) + 1), + ByteEnumField('subtype', 0x00, LLDP_CHASSIS_ID_TLV_SUBTYPES), + XStrLenField('id', '', length_from=lambda pkt: pkt._length - 1) + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and len(self.id) == 0: + raise LLDPInvalidLengthField('id must be >= 1 characters long') + + def post_dissect(self, s): + self._check() + return super(LLDPDUChassisID, self).post_dissect(s) + + def do_build(self): + self._check() + return super(LLDPDUChassisID, self).do_build() + + +class LLDPDUPortID(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.3 / p. 26 + """ + LLDP_PORT_ID_TLV_SUBTYPES = { + 0x00: 'reserved', + 0x01: 'interface alias', + 0x02: 'port component', + 0x03: 'MAC address', + 0x04: 'network address', + 0x05: 'interface name', + 0x06: 'agent circuit ID', + 0x07: 'locally assigned', + range(0x08, 0xff): 'reserved' + } + + SUBTYPE_RESERVED = 0x00 + SUBTYPE_INTERFACE_ALIAS = 0x01 + SUBTYPE_PORT_COMPONENT = 0x02 + SUBTYPE_MAC_ADDRESS = 0x03 + SUBTYPE_NETWORK_ADDRESS = 0x04 + SUBTYPE_INTERFACE_NAME = 0x05 + SUBTYPE_AGENT_CIRCUIT_ID = 0x06 + SUBTYPE_LOCALLY_ASSIGNED = 0x07 + + fields_desc = [ + BitEnumField('_type', 0x02, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='id', + adjust=lambda pkt, x: len(pkt.id) + 1), + ByteEnumField('subtype', 0x00, LLDP_PORT_ID_TLV_SUBTYPES), + StrLenField('id', '', length_from=lambda pkt: pkt._length - 1) + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and len(self.id) == 0: + raise LLDPInvalidLengthField('id must be >= 1 characters long') + + def post_dissect(self, s): + self._check() + return super(LLDPDUPortID, self).post_dissect(s) + + def do_build(self): + self._check() + return super(LLDPDUPortID, self).do_build() + + +class LLDPDUTimeToLive(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.4 / p. 29 + """ + fields_desc = [ + BitEnumField('_type', 0x03, 7, LLDPDU.TYPES), + BitField('_length', 0x02, 9), + ShortField('ttl', 20) + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and self._length != 2: + raise LLDPInvalidLengthField('length must be 2 - got ' + '{}'.format(self._length)) + + def post_dissect(self, s): + self._check() + return super(LLDPDUTimeToLive, self).post_dissect(s) + + def do_build(self): + self._check() + return super(LLDPDUTimeToLive, self).do_build() + + +class LLDPDUEndOfLLDPDU(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.1 / p. 26 + """ + fields_desc = [ + BitEnumField('_type', 0x00, 7, LLDPDU.TYPES), + BitField('_length', 0x00, 9), + ] + + def extract_padding(self, s): + return '', s + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and self._length != 0: + raise LLDPInvalidLengthField('length must be 0 - got ' + '{}'.format(self._length)) + + def post_dissect(self, s): + self._check() + return super(LLDPDUEndOfLLDPDU, self).post_dissect(s) + + def do_build(self): + self._check() + return super(LLDPDUEndOfLLDPDU, self).do_build() + + +class LLDPDUPortDescription(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.5 / p. 29 + """ + fields_desc = [ + BitEnumField('_type', 0x04, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='description'), + StrLenField('description', '', length_from=lambda pkt: pkt._length) + ] + + +class LLDPDUSystemName(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.6 / p. 30 + """ + fields_desc = [ + BitEnumField('_type', 0x05, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='name'), + StrLenField('name', '', length_from=lambda pkt: pkt._length) + ] + + +class LLDPDUSystemDescription(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.7 / p. 31 + """ + fields_desc = [ + BitEnumField('_type', 0x06, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='name'), + StrLenField('description', '', length_from=lambda pkt: pkt._length) + ] + + +class LLDPDUSystemCapabilities(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.8 / p. 31 + """ + fields_desc = [ + BitEnumField('_type', 0x07, 7, LLDPDU.TYPES), + BitFieldLenField('_length', 4, 9), + BitField('reserved_5_available', 0, 1), + BitField('reserved_4_available', 0, 1), + BitField('reserved_3_available', 0, 1), + BitField('reserved_2_available', 0, 1), + BitField('reserved_1_available', 0, 1), + BitField('two_port_mac_relay_available', 0, 1), + BitField('s_vlan_component_available', 0, 1), + BitField('c_vlan_component_available', 0, 1), + BitField('station_only_available', 0, 1), + BitField('docsis_cable_device_available', 0, 1), + BitField('telephone_available', 0, 1), + BitField('router_available', 0, 1), + BitField('wlan_access_point_available', 0, 1), + BitField('mac_bridge_available', 0, 1), + BitField('repeater_available', 0, 1), + BitField('other_available', 0, 1), + BitField('reserved_5_enabled', 0, 1), + BitField('reserved_4_enabled', 0, 1), + BitField('reserved_3_enabled', 0, 1), + BitField('reserved_2_enabled', 0, 1), + BitField('reserved_1_enabled', 0, 1), + BitField('two_port_mac_relay_enabled', 0, 1), + BitField('s_vlan_component_enabled', 0, 1), + BitField('c_vlan_component_enabled', 0, 1), + BitField('station_only_enabled', 0, 1), + BitField('docsis_cable_device_enabled', 0, 1), + BitField('telephone_enabled', 0, 1), + BitField('router_enabled', 0, 1), + BitField('wlan_access_point_enabled', 0, 1), + BitField('mac_bridge_enabled', 0, 1), + BitField('repeater_enabled', 0, 1), + BitField('other_enabled', 0, 1), + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and self._length != 4: + raise LLDPInvalidLengthField('length must be 4 - got ' + '{}'.format(self._length)) + + def post_dissect(self, s): + self._check() + return super(LLDPDUSystemCapabilities, self).post_dissect(s) + + def do_build(self): + self._check() + return super(LLDPDUSystemCapabilities, self).do_build() + + +class LLDPDUManagementAddress(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.9 / p. 32 + + currently only 0x00..0x1e are used by standards, no way to + use anything > 0xff as management address subtype is only + one octet wide + + see https://www.iana.org/assignments/ + address-family-numbers/address-family-numbers.xhtml + """ + IANA_ADDRESS_FAMILY_NUMBERS = { + 0x00: 'other', + 0x01: 'IPv4', + 0x02: 'IPv6', + 0x03: 'NSAP', + 0x04: 'HDLC', + 0x05: 'BBN', + 0x06: '802', + 0x07: 'E.163', + 0x08: 'E.164', + 0x09: 'F.69', + 0x0a: 'X.121', + 0x0b: 'IPX', + 0x0c: 'Appletalk', + 0x0d: 'Decnet IV', + 0x0e: 'Banyan Vines', + 0x0f: 'E.164 with NSAP', + 0x10: 'DNS', + 0x11: 'Distinguished Name', + 0x12: 'AS Number', + 0x13: 'XTP over IPv4', + 0x14: 'XTP over IPv6', + 0x15: 'XTP native mode XTP', + 0x16: 'Fiber Channel World-Wide Port Name', + 0x17: 'Fiber Channel World-Wide Node Name', + 0x18: 'GWID', + 0x19: 'AFI for L2VPN', + 0x1a: 'MPLS-TP Section Endpoint ID', + 0x1b: 'MPLS-TP LSP Endpoint ID', + 0x1c: 'MPLS-TP Pseudowire Endpoint ID', + 0x1d: 'MT IP Multi-Topology IPv4', + 0x1e: 'MT IP Multi-Topology IPv6' + } + + SUBTYPE_MANAGEMENT_ADDRESS_OTHER = 0x00 + SUBTYPE_MANAGEMENT_ADDRESS_IPV4 = 0x01 + SUBTYPE_MANAGEMENT_ADDRESS_IPV6 = 0x02 + SUBTYPE_MANAGEMENT_ADDRESS_NSAP = 0x03 + SUBTYPE_MANAGEMENT_ADDRESS_HDLC = 0x04 + SUBTYPE_MANAGEMENT_ADDRESS_BBN = 0x05 + SUBTYPE_MANAGEMENT_ADDRESS_802 = 0x06 + SUBTYPE_MANAGEMENT_ADDRESS_E_163 = 0x07 + SUBTYPE_MANAGEMENT_ADDRESS_E_164 = 0x08 + SUBTYPE_MANAGEMENT_ADDRESS_F_69 = 0x09 + SUBTYPE_MANAGEMENT_ADDRESS_X_121 = 0x0A + SUBTYPE_MANAGEMENT_ADDRESS_IPX = 0x0B + SUBTYPE_MANAGEMENT_ADDRESS_APPLETALK = 0x0C + SUBTYPE_MANAGEMENT_ADDRESS_DECNET_IV = 0x0D + SUBTYPE_MANAGEMENT_ADDRESS_BANYAN_VINES = 0x0E + SUBTYPE_MANAGEMENT_ADDRESS_E_164_WITH_NSAP = 0x0F + SUBTYPE_MANAGEMENT_ADDRESS_DNS = 0x10 + SUBTYPE_MANAGEMENT_ADDRESS_DISTINGUISHED_NAME = 0x11 + SUBTYPE_MANAGEMENT_ADDRESS_AS_NUMBER = 0x12 + SUBTYPE_MANAGEMENT_ADDRESS_XTP_OVER_IPV4 = 0x13 + SUBTYPE_MANAGEMENT_ADDRESS_XTP_OVER_IPV6 = 0x14 + SUBTYPE_MANAGEMENT_ADDRESS_XTP_NATIVE_MODE_XTP = 0x15 + SUBTYPE_MANAGEMENT_ADDRESS_FIBER_CHANNEL_WORLD_WIDE_PORT_NAME = 0x16 + SUBTYPE_MANAGEMENT_ADDRESS_FIBER_CHANNEL_WORLD_WIDE_NODE_NAME = 0x17 + SUBTYPE_MANAGEMENT_ADDRESS_GWID = 0x18 + SUBTYPE_MANAGEMENT_ADDRESS_AFI_FOR_L2VPN = 0x19 + SUBTYPE_MANAGEMENT_ADDRESS_MPLS_TP_SECTION_ENDPOINT_ID = 0x1A + SUBTYPE_MANAGEMENT_ADDRESS_MPLS_TP_LSP_ENDPOINT_ID = 0x1B + SUBTYPE_MANAGEMENT_ADDRESS_MPLS_TP_PSEUDOWIRE_ENDPOINT_ID = 0x1C + SUBTYPE_MANAGEMENT_ADDRESS_MT_IP_MULTI_TOPOLOGY_IPV4 = 0x1D + SUBTYPE_MANAGEMENT_ADDRESS_MT_IP_MULTI_TOPOLOGY_IPV6 = 0x1E + + INTERFACE_NUMBERING_SUBTYPES = { + 0x01: 'unknown', + 0x02: 'ifIndex', + 0x03: 'system port number' + } + + SUBTYPE_INTERFACE_NUMBER_UNKNOWN = 0x01 + SUBTYPE_INTERFACE_NUMBER_IF_INDEX = 0x02 + SUBTYPE_INTERFACE_NUMBER_SYSTEM_PORT_NUMBER = 0x03 + + ''' + Note - calculation of _length field: + _length = 1@_management_address_string_length + + 1@management_address_subtype + + management_address.len + + 1@interface_numbering_subtype + + 4@interface_number + + 1@_oid_string_length + + object_id.len + ''' + + fields_desc = [ + BitEnumField('_type', 0x08, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='management_address', + adjust=lambda pkt, x: + 8 + len(pkt.management_address) + len(pkt.object_id)), + BitFieldLenField('_management_address_string_length', None, 8, + length_of='management_address', + adjust=lambda pkt, x: len(pkt.management_address) + 1), + ByteEnumField('management_address_subtype', 0x00, + IANA_ADDRESS_FAMILY_NUMBERS), + XStrLenField('management_address', '', + length_from=lambda pkt: + pkt._management_address_string_length - 1), + ByteEnumField('interface_numbering_subtype', + SUBTYPE_INTERFACE_NUMBER_UNKNOWN, + INTERFACE_NUMBERING_SUBTYPES), + BitField('interface_number', 0, 32), + BitFieldLenField('_oid_string_length', None, 8, length_of='object_id'), + XStrLenField('object_id', '', + length_from=lambda pkt: pkt._oid_string_length), + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode(): + management_address_len = len(self.management_address) + if management_address_len == 0 or management_address_len > 31: + raise LLDPInvalidLengthField( + 'management address must be 1..31 characters long - ' + 'got string of size {}'.format(management_address_len)) + + def post_dissect(self, s): + self._check() + return super(LLDPDUManagementAddress, self).post_dissect(s) + + def do_build(self): + self._check() + return super(LLDPDUManagementAddress, self).do_build() + +LLDPDU_CLASS_TYPES = { + 0x00: LLDPDUEndOfLLDPDU, + 0x01: LLDPDUChassisID, + 0x02: LLDPDUPortID, + 0x03: LLDPDUTimeToLive, + 0x04: LLDPDUPortDescription, + 0x05: LLDPDUSystemName, + 0x06: LLDPDUSystemDescription, + 0x07: LLDPDUSystemCapabilities, + 0x08: LLDPDUManagementAddress, + range(0x09, 0x7e): None, # reserved - future standardization + 127: None # organisation specific TLV +} + +class LLDPConfiguration(object): + """ + basic configuration for LLDP layer + """ + def __init__(self): + self._strict_mode = True + self.strict_mode_enable() + + def strict_mode_enable(self): + """ + enable strict mode and dissector debugging + """ + self._strict_mode = True + conf.debug_dissector = True + + def strict_mode_disable(self): + """ + disable strict mode and dissector debugging + """ + self._strict_mode = False + conf.debug_dissector = False + + def strict_mode(self): + """ + get current strict mode state + """ + return self._strict_mode + + +conf.contribs['LLDP'] = LLDPConfiguration() + +bind_layers(Ether, LLDPDU, type=LLDP_ETHER_TYPE) +bind_layers(Dot1Q, LLDPDU, type=LLDP_ETHER_TYPE) diff --git a/scapy/contrib/lldp.uts b/scapy/contrib/lldp.uts new file mode 100644 index 0000000000000000000000000000000000000000..20f2c64ce51e161e62de719235d06af7bf820c2f --- /dev/null +++ b/scapy/contrib/lldp.uts @@ -0,0 +1,164 @@ +from enum import test +% LLDP test campaign + +# +# execute test: +# > test/run_tests -P "load_contrib('lldp')" -t scapy/contrib/lldp.uts +# + ++ Basic layer handling += build basic LLDP frames + +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/ \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ + LLDPDUTimeToLive()/\ + LLDPDUSystemName(name='mate')/\ + LLDPDUSystemCapabilities(telephone_available=1, router_available=1, telephone_enabled=1)/\ + LLDPDUManagementAddress( + management_address_subtype=LLDPDUManagementAddress.SUBTYPE_MANAGEMENT_ADDRESS_IPV4, + management_address='1.2.3.4', + interface_numbering_subtype=LLDPDUManagementAddress.SUBTYPE_INTERFACE_NUMBER_IF_INDEX, + interface_number=23, + object_id='abcd') / \ + LLDPDUEndOfLLDPDU() + +frm = frm.build() +frm = Ether(frm) + += add padding if required + +conf.contribs['LLDP'].strict_mode_disable() +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0') / \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \ + LLDPDUTimeToLive() / \ + LLDPDUEndOfLLDPDU() +assert(len(str(frm)) == 60) +assert(len(str(Ether(str(frm))[Padding])) == 24) + +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth012345678901234567890123') / \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \ + LLDPDUTimeToLive() / \ + LLDPDUEndOfLLDPDU() +assert (len(str(frm)) == 60) +assert (len(str(Ether(str(frm))[Padding])) == 1) + +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0123456789012345678901234') / \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \ + LLDPDUTimeToLive() / \ + LLDPDUEndOfLLDPDU() +assert (len(str(frm)) == 60) +try: + Ether(str(frm))[Padding] + assert False +except IndexError: + pass + + ++ strict mode handling - build += basic frame structure + +conf.contribs['LLDP'].strict_mode_enable() + +# invalid lenght in LLDPDUEndOfLLDPDU +try: + frm = Ether()/LLDPDUChassisID(id='slartibart')/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU(_length=8) + frm.build() + assert False +except LLDPInvalidLengthField: + pass + +# missing chassis id +try: + frm = Ether()/LLDPDUChassisID()/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() + frm.build() + assert False +except LLDPInvalidLengthField: + pass + +# missing management address +try: + frm = Ether()/LLDPDUChassisID(id='slartibart')/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUManagementAddress()/LLDPDUEndOfLLDPDU() + frm.build() + assert False +except LLDPInvalidLengthField: + pass + ++ strict mode handling - dissect += basic frame structure + +conf.contribs['LLDP'].strict_mode_enable() +# missing PortIDTLV +try: + frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidFrameStructure: + pass + +# invalid order +try: + frm = Ether() / LLDPDUPortID(id='42') / LLDPDUChassisID(id='slartibart') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidFrameStructure: + pass + +# layer LLDPDUPortID occures twice +try: + frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / LLDPDUPortID(id='23') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidFrameStructure: + pass + +# missing LLDPDUEndOfLLDPDU +try: + frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / \ + LLDPDUPortID(id='23') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidFrameStructure: + pass + +conf.contribs['LLDP'].strict_mode_disable() +frm = Ether()/LLDPDUChassisID()/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() +frm = Ether(frm.build()) + += length fields / value sizes checks + +conf.contribs['LLDP'].strict_mode_enable() +# missing chassis id => invalid length +try: + frm = Ether() / LLDPDUChassisID() / LLDPDUPortID(id='42') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidLengthField: + pass + +# invalid lenght in LLDPDUEndOfLLDPDU +try: + frm = Ether()/LLDPDUChassisID(id='slartibart')/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU(_length=8) + Ether(frm.build()) + assert False +except LLDPInvalidLengthField: + pass + +# invalid management address +try: + frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / LLDPDUTimeToLive() / LLDPDUManagementAddress() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + +frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() +frm = Ether(frm.build()) + +frm = Ether() / LLDPDUChassisID() / LLDPDUPortID() / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU(_length=8) +frm = Ether(frm.build())