text: #! /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 (see LLDPDUGenericOrganisationSpecific for a starting point)
  
      :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.error import log_runtime, Scapy_Exception
  from scapy.layers.l2 import Ether, Dot1Q
  from scapy.fields import MACField, IPField, BitField, \
           StrLenField, ByteEnumField, BitEnumField, \
           EnumField, ThreeBytesField, BitFieldLenField, \
           ShortField, XStrLenField
  from scapy.packet import Packet, Padding, bind_layers
  from scapy.modules.six.moves import range
  from scapy.data import ETHER_TYPES
  from scapy.compat import orb
  
  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
          try:
              lldpdu_tlv_type = orb(payload[0]) // 2
              return LLDPDU_CLASS_TYPES.get(lldpdu_tlv_type, conf.raw_layer)
          except IndexError:
              return conf.raw_layer
  
      @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 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
  
          if isinstance(under_layer, 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:
              return pkt + pay + b'\x00' * (LLDPDU.ETHER_FRAME_MIN_LEN - frame_size)
          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:
                  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:
                      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)
  
      def _check(self):
          """Overwrited by LLDPU objects"""
          pass
  
      def post_dissect(self, s):
          self._check()
          return super(LLDPDU, self).post_dissect(s)
  
      def do_build(self):
          self._check()
          return super(LLDPDU, self).do_build()
  
  class _LLDPidField(StrLenField):
      """Class that selects the type of the ID field depending
      on the type of `subtype`"""
      __slots__ = StrLenField.__slots__ + ["subtypes_dict"]
  
      def __init__(self, name, default, subtypes_dict, *args, **kargs):
          self.subtypes_dict = subtypes_dict
          super(_LLDPidField, self).__init__(name, default, *args, **kargs)
  
      def m2i(self, pkt, x):
          cls = self.subtypes_dict.get(pkt.subtype, StrLenField)
          try:
              return (cls.m2i.__func__ if six.PY2 else cls.m2i)(self, pkt, x)
          except:
              log_runtime.exception("Failed to dissect " + self.name + " ! ")
              return StrLenField.m2i(self, pkt, x)
  
      def i2m(self, pkt, x):
          cls = self.subtypes_dict.get(pkt.subtype, StrLenField)
          try:
              return (cls.i2m.__func__ if six.PY2 else cls.i2m)(self, pkt, x)
          except:
              log_runtime.exception("Failed to build " + self.name + " ! ")
              return StrLenField.i2m(self, pkt, x)
  
  def _ldp_id_adjustlen(pkt, x):
      """Return the length of the `id` field,
      according to its real encoded type"""
      f, v = pkt.getfield_and_val('id')
      return len(_LLDPidField.i2m(f, pkt, v)) + 1
  
  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'
      }
  
      LLDP_CHASSIS_ID_TLV_SUBTYPES_FIELDS = {
          0x04: MACField,
          0x05: IPField,
      }
  
      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: _ldp_id_adjustlen(pkt, x)),
          ByteEnumField('subtype', 0x00, LLDP_CHASSIS_ID_TLV_SUBTYPES),
          _LLDPidField('id', '', LLDP_CHASSIS_ID_TLV_SUBTYPES_FIELDS, length_from=lambda pkt: pkt._length - 1)
      ]
  
      def _check(self):
          """
          run layer specific checks
          """
          if conf.contribs['LLDP'].strict_mode() and not self.id:
              raise LLDPInvalidLengthField('id must be >= 1 characters long')
  
  
  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'
      }
  
      LLDP_PORT_ID_TLV_SUBTYPES = {
          0x03: MACField,
          0x04: IPField,
      }
  
      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: _ldp_id_adjustlen(pkt, x)),
          ByteEnumField('subtype', 0x00, LLDP_PORT_ID_TLV_SUBTYPES),
          _LLDPidField('id', '', LLDP_PORT_ID_TLV_SUBTYPES, length_from=lambda pkt: pkt._length - 1)
      ]
  
      def _check(self):
          """
          run layer specific checks
          """
          if conf.contribs['LLDP'].strict_mode() and not self.id:
              raise LLDPInvalidLengthField('id must be >= 1 characters long')
  
  
  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))
  
  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))
  
  
  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='system_name'),
          StrLenField('system_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='description'),
          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))
  
  
  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))
  
  
  class ThreeBytesEnumField(EnumField, ThreeBytesField):
  
      def __init__(self, name, default, enum):
          EnumField.__init__(self, name, default, enum, "!I")
  
  
  class LLDPDUGenericOrganisationSpecific(LLDPDU):
  
      ORG_UNIQUE_CODE_PNO = 0x000ecf
      ORG_UNIQUE_CODE_IEEE_802_1 = 0x0080c2
      ORG_UNIQUE_CODE_IEEE_802_3 = 0x00120f
      ORG_UNIQUE_CODE_TIA_TR_41_MED = 0x0012bb
      ORG_UNIQUE_CODE_HYTEC = 0x30b216
  
      ORG_UNIQUE_CODES = {
          ORG_UNIQUE_CODE_PNO: "PROFIBUS International (PNO)",
          ORG_UNIQUE_CODE_IEEE_802_1: "IEEE 802.1",
          ORG_UNIQUE_CODE_IEEE_802_3: "IEEE 802.3",
          ORG_UNIQUE_CODE_TIA_TR_41_MED: "TIA TR-41 Committee . Media Endpoint Discovery",
          ORG_UNIQUE_CODE_HYTEC: "Hytec Geraetebau GmbH"
      }
  
      fields_desc = [
          BitEnumField('_type', 127, 7, LLDPDU.TYPES),
          BitFieldLenField('_length', None, 9, length_of='data', adjust=lambda pkt, x: len(pkt.data) + 4),
          ThreeBytesEnumField('org_code', 0, ORG_UNIQUE_CODES),
          ByteField('subtype', 0x00),
          XStrLenField('data', '', length_from=lambda pkt: pkt._length - 4)
      ]
  
  # 0x09 .. 0x7e is reserved for future standardization and for now treated as Raw() data
  LLDPDU_CLASS_TYPES = {
      0x00: LLDPDUEndOfLLDPDU,
      0x01: LLDPDUChassisID,
      0x02: LLDPDUPortID,
      0x03: LLDPDUTimeToLive,
      0x04: LLDPDUPortDescription,
      0x05: LLDPDUSystemName,
      0x06: LLDPDUSystemDescription,
      0x07: LLDPDUSystemCapabilities,
      0x08: LLDPDUManagementAddress,
      127: LLDPDUGenericOrganisationSpecific
  }
  
  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)
  