diff --git a/scapy/contrib/lldp.py b/scapy/contrib/lldp.py index 6ab62755419..3c94af41db2 100644 --- a/scapy/contrib/lldp.py +++ b/scapy/contrib/lldp.py @@ -20,7 +20,8 @@ :TODO: - | organization specific TLV e.g. ProfiNet - | (see LLDPDUGenericOrganisationSpecific for a starting point) + | (see LLDPDUGenericOrganisationSpecific for a starting point; + | IEEE 802.1Q and IEEE 802.1AX org-specific TLVs are implemented) - Ignore everything after EndofLLDPDUTLV :NOTES: @@ -40,13 +41,12 @@ from scapy.layers.l2 import Ether, Dot1Q from scapy.fields import MACField, IPField, IP6Field, BitField, \ StrLenField, ByteEnumField, BitEnumField, \ - EnumField, ThreeBytesField, BitFieldLenField, \ + BitFieldLenField, \ ShortField, XStrLenField, ByteField, ConditionalField, \ MultipleTypeField, FlagsField, ShortEnumField, ScalingField, \ - BitScalingField -from scapy.packet import Packet, bind_layers + BitScalingField, FieldLenField, IntField, XIntField, PacketListField, OUIField +from scapy.packet import NoPayload, Packet, bind_layers from scapy.data import ETHER_TYPES -from scapy.compat import orb, bytes_int LLDP_NEAREST_BRIDGE_MAC = '01:80:c2:00:00:0e' LLDP_NEAREST_NON_TPMR_BRIDGE_MAC = '01:80:c2:00:00:03' @@ -55,6 +55,40 @@ LLDP_ETHER_TYPE = 0x88cc ETHER_TYPES[LLDP_ETHER_TYPE] = 'LLDP' +_LLDP_ETS_TSA_ALGORITHMS = { + 0: 'Strict Priority', + 1: 'Credit-Based Shaper', + 2: 'Enhanced Transmission Selection', + 3: 'ATS Transmission Selection', + # 4-254: Reserved for future standardization + 255: 'Vendor Specific', # used with DCBX +} + +ORG_UNIQUE_CODES = { + 0x000ecf: "PROFIBUS International (PNO)", + 0x0080c2: "IEEE 802.1", + 0x00120f: "IEEE 802.3", + 0x0012bb: "TIA TR-41 Committee - Media Endpoint Discovery", + 0x30b216: "Hytec Geraetebau GmbH", +} + +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 + +_LLDP_SEL_FIELD_VALUES = { + 0: 'Reserved', + 1: 'Default or Ethertype', + 2: 'TCP/SCTP port', + 3: 'UDP/DCCP port', + 4: 'TCP/SCTP/UDP/DCCP port', + 5: 'DSCP', + 6: 'Reserved', + 7: 'Reserved', +} + class LLDPInvalidFieldValue(Scapy_Exception): """ @@ -154,7 +188,7 @@ class LLDPDU(Packet): 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 + lldpdu_tlv_type = payload[0] // 2 class_type = LLDPDU_CLASS_TYPES.get(lldpdu_tlv_type, conf.raw_layer) if isinstance(class_type, list): for cls in class_type: @@ -650,9 +684,13 @@ class LLDPDUManagementAddress(LLDPDU): 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), # noqa: E501 + 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, LLDPDU.IANA_ADDRESS_FAMILY_NUMBERS), XStrLenField('management_address', '', @@ -680,41 +718,830 @@ def _check(self): 'got string of size {}'.format(management_address_len)) -class ThreeBytesEnumField(EnumField, ThreeBytesField): +class LLDPDUGenericOrganisationSpecific(LLDPDU): + SUBTYPE = None # type: int | None + ORG_CODE = None # type: int | None + EXPECTED_LENGTH = None # type: int | None - def __init__(self, name, default, enum): - EnumField.__init__(self, name, default, enum, "!I") + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitFieldLenField( + '_length', + None, + 9, + length_of='data', + adjust=lambda pkt, x: len(pkt.data) + 4 + ), + OUIField('org_code', 0), + ByteField('subtype', 0x00), + XStrLenField( + 'data', + '', + length_from=lambda pkt: 0 if pkt._length is None else + pkt._length - 4 + ) + ] + @classmethod + def _match_organization_specific(cls, payload): + if cls.SUBTYPE is None or cls.ORG_CODE is None: + return True # base class: accept anything + if payload[5] != cls.SUBTYPE: + return False + if int.from_bytes(payload[2:5], 'big') != cls.ORG_CODE: + return False + if cls.EXPECTED_LENGTH is not None: + return _lldp_tlv_length(payload) == cls.EXPECTED_LENGTH + return True -class LLDPDUGenericOrganisationSpecific(LLDPDU): + def _check(self): + if (self.EXPECTED_LENGTH is not None + and conf.contribs['LLDP'].strict_mode() + and self._length is not None + and self._length != self.EXPECTED_LENGTH): + raise LLDPInvalidLengthField( + '{} TLV length must be {}, got {}'.format( + type(self).__name__, self.EXPECTED_LENGTH, self._length)) + + +def _lldp_tlv_length(payload): + """Extract the 9-bit TLV length from the two-byte LLDP TLV header. + + The LLDP TLV header is 16 bits: bits 15-9 carry the 7-bit type and + bits 8-0 carry the 9-bit length (IEEE 802.1AB-2016 section 9.2). + In wire bytes: bit 8 of the length sits in the LSB of payload[0], + and bits 7-0 sit in payload[1]. + """ + return ((payload[0] & 0x01) << 8) | payload[1] + + +class LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: Port VLAN ID (PVID). + + Carries the native (untagged) VLAN ID of the port (the VLAN ID that + 802.1Q Dot1Q sniffing cannot observe, because frames on the native VLAN + carry no 802.1Q header). + + IEEE 802.1Q D.2.1. + + Payload layout (6 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x01 + bytes 4-5 PVID (big-endian) + """ + + SUBTYPE = 0x01 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 6 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitField('_length', 6, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x01), + ShortField('pvid', 0), + ] + + +class LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID( + LLDPDUGenericOrganisationSpecific +): + """ + IEEE 802.1 organizationally specific TLV: Port and Protocol VLAN ID. + + Advertises the Port and Protocol VLAN ID (PPVID) together with flags + indicating whether the port supports and has enabled the protocol VLAN. + Multiple instances may appear in one LLDPDU, one per protocol VLAN. + + IEEE 802.1Q D.2.2. + + Payload layout (7 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x02 + byte 4 flags: bits 7-3: reserved, bit 2: ppvid_enabled, + bit 1: ppvid_supported, bit 0: reserved + bytes 5-6 PPVID (big-endian, 0 if unknown) + """ + + SUBTYPE = 0x02 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 7 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitField('_length', 7, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x02), + BitField('reserved', 0, 5), + BitField('ppvid_enabled', 0, 1), + BitField('ppvid_supported', 0, 1), + BitField('reserved0', 0, 1), + ShortField('ppvid', 0), + ] + + +class LLDPDUOrgSpecific_IEEE8021_VLAN_Name(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: VLAN Name. + + Carries a VLAN ID together with its human-readable name. One TLV is + emitted per VLAN, so a single LLDP frame from a trunk port can enumerate + the switch's complete VLAN table. + + IEEE 802.1Q D.2.3. + + Payload layout (7 + len(vlan_name) bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x03 + bytes 4-5 VLAN ID (big-endian) + byte 6 vlan_name_length (0-32) + bytes 7..N vlan_name + """ + + SUBTYPE = 0x03 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='vlan_name', + adjust=lambda pkt, x: x + 7), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x03), + ShortField('vlan_id', 0), + FieldLenField('vlan_name_length', None, length_of='vlan_name', fmt='B'), + StrLenField('vlan_name', b'', length_from=lambda pkt: pkt.vlan_name_length), + ] + + def _check(self): + if not conf.contribs['LLDP'].strict_mode(): + return + if self._length is not None: + if self._length < 7: + raise LLDPInvalidLengthField( + 'IEEE 802.1 VLAN Name TLV length must be >= 7, ' + 'got {}'.format(self._length)) + if self._length > 39: + raise LLDPInvalidLengthField( + 'IEEE 802.1 VLAN Name TLV length must be <= 39 ' + '(vlan_name max 32 bytes), got {}'.format(self._length)) + # D.2.3.5: each VID+name combination must be unique in the frame + root = self + while root.underlayer is not None: + root = root.underlayer + layer = root + while layer is not None and not isinstance(layer, NoPayload): + if (layer is not self + and isinstance(layer, + LLDPDUOrgSpecific_IEEE8021_VLAN_Name) + and layer.vlan_id == self.vlan_id + and layer.vlan_name == self.vlan_name): + raise LLDPInvalidFrameStructure( + 'Duplicate VLAN Name TLV: VID={} name={!r}'.format( + self.vlan_id, self.vlan_name)) + layer = layer.payload + + +class LLDPDUOrgSpecific_IEEE8021_Protocol_Identity(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: Protocol Identity. + + Identifies a protocol accessible through the port by carrying its raw + protocol-identity octets (e.g. the first few bytes of a PDU header). + Multiple instances may appear in one LLDPDU, one per protocol. - 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", # noqa: E501 - ORG_UNIQUE_CODE_HYTEC: "Hytec Geraetebau GmbH" + IEEE 802.1Q D.2.4. + + Payload layout (5 to 260 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x04 + byte 4 protocol_identity_length + bytes 5..N protocol_identity (0-255 bytes) + """ + + SUBTYPE = 0x04 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='protocol_identity', + adjust=lambda pkt, x: x + 5), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x04), + FieldLenField('protocol_identity_length', None, + length_of='protocol_identity', fmt='B'), + XStrLenField('protocol_identity', b'', + length_from=lambda pkt: pkt.protocol_identity_length), + ] + + @classmethod + def _match_organization_specific(cls, payload): + length = _lldp_tlv_length(payload) + return (payload[5] == cls.SUBTYPE + and 5 <= length <= 260 + and int.from_bytes(payload[2:5], 'big') == cls.ORG_CODE) + + def _check(self): + if conf.contribs['LLDP'].strict_mode(): + if (self._length is not None + and not (5 <= self._length <= 260)): + raise LLDPInvalidLengthField( + 'IEEE 802.1 Protocol Identity TLV length must be between 5 and 260,' + ' got {}'.format(self._length)) + if len(self.protocol_identity) > 255: + raise LLDPInvalidLengthField( + 'protocol_identity max 255 bytes (got {})'.format( + len(self.protocol_identity))) + + +class LLDPDUOrgSpecific_IEEE8021_VID_Usage_Digest(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: VID Usage Digest. + + Advertises the VID Usage Digest associated with the port. The digest is + the CRC32 of the 512-octet VID Usage Table (128 entries x 4 bytes). + + IEEE 802.1Q D.2.5. + + Payload layout (8 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x05 + bytes 4-7 vid_usage_digest (CRC32, big-endian) + """ + + SUBTYPE = 0x05 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 8 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitField('_length', 8, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x05), + XIntField('vid_usage_digest', 0), + ] + + +class LLDPDUOrgSpecific_IEEE8021_Management_VID(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: Management VID. + + Advertises the VLAN ID associated with the management address of the + device. A value of 0 means the device has no management VID. + + IEEE 802.1Q D.2.6. + + Payload layout (6 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x06 + bytes 4-5 management_vid (big-endian, 0 if none) + """ + + SUBTYPE = 0x06 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 6 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitField('_length', 6, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x06), + ShortField('management_vid', 0), + ] + + +class LLDPDUOrgSpecific_IEEE8021_Link_Aggregation(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: Link Aggregation. + + Advertises whether the port supports link aggregation, whether it is + currently aggregated, and the ID of the aggregated port (0 if not + aggregated). + + IEEE 802.1AX. + + Payload layout (9 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x07 + byte 4 aggregation status (bit 0: capable, bit 1: enabled) + bytes 5-8 aggregated_port_id (big-endian, 0 if not aggregated) + """ + + AGG_STATUS = { + (1 << 0): 'capable', + (1 << 1): 'enabled', } + SUBTYPE = 0x07 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 9 + fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), - BitFieldLenField('_length', None, 9, length_of='data', adjust=lambda pkt, x: len(pkt.data) + 4), # noqa: E501 - ThreeBytesEnumField('org_code', 0, ORG_UNIQUE_CODES), - ByteField('subtype', 0x00), - XStrLenField('data', '', - length_from=lambda pkt: 0 if pkt._length is None else - pkt._length - 4) + BitField('_length', 9, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x07), + FlagsField('aggregation_status', 0, 8, AGG_STATUS), + IntField('aggregated_port_id', 0), ] - @staticmethod - def _match_organization_specific(payload): - return True + +class LLDPDUOrgSpecific_IEEE8021_Congestion_Notification( + LLDPDUGenericOrganisationSpecific +): + """ + IEEE 802.1 organizationally specific TLV: Congestion Notification. + + Advertises per-priority Congestion Notification Point Variable (CNPV) + and Ready indicators for all 8 802.1p priorities on the port. + + IEEE 802.1Q D.2.7. + + Payload layout (6 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x08 + byte 4 CNPV indicators (bit 0: prio 0, ..., bit 7: prio 7) + byte 5 ready indicators (bit 0: prio 0, ..., bit 7: prio 7) + """ + + PRIORITY_BITS = { + (1 << 0): 'prio0', (1 << 1): 'prio1', + (1 << 2): 'prio2', (1 << 3): 'prio3', + (1 << 4): 'prio4', (1 << 5): 'prio5', + (1 << 6): 'prio6', (1 << 7): 'prio7', + } + + SUBTYPE = 0x08 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 6 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitField('_length', 6, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x08), + FlagsField('cnpv_indicators', 0, 8, PRIORITY_BITS), + FlagsField('ready_indicators', 0, 8, PRIORITY_BITS), + ] + + +class LLDPDUOrgSpecific_IEEE8021_ETS_Configuration(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: ETS Configuration. + + Advertises the Enhanced Transmission Selection configuration for the port: + the priority-to-traffic-class mapping, per-TC bandwidth allocation, and + the Transmission Selection Algorithm (TSA) for each TC. + + IEEE 802.1Q D.2.8. + + Payload layout (25 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x09 + byte 4 flags: bit 7: willing, bit 6: CBS, bits 5-3: reserved, + bits 2-0: max_tcs + bytes 5-8 priority assignment table (4 bits per priority, prio 0-7) + bytes 9-16 TC bandwidth table (1 byte per TC, TC 0-7) + bytes 17-24 TSA assignment table (1 byte per TC, TC 0-7) + """ + + SUBTYPE = 0x09 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 25 + + TSA_ALGORITHMS = _LLDP_ETS_TSA_ALGORITHMS + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitField('_length', 25, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x09), + # flags byte: bit7=willing, bit6=CBS, bits5-3=reserved, bits2-0=maxTCs + BitField('willing', 0, 1), + BitField('cbs', 0, 1), + BitField('reserved', 0, 3), + BitField('max_tcs', 0, 3), + # priority assignment table: 4 bits per priority, MSB=prio0 + BitField('prio0_tc', 0, 4), + BitField('prio1_tc', 0, 4), + BitField('prio2_tc', 0, 4), + BitField('prio3_tc', 0, 4), + BitField('prio4_tc', 0, 4), + BitField('prio5_tc', 0, 4), + BitField('prio6_tc', 0, 4), + BitField('prio7_tc', 0, 4), + # TC bandwidth table: percentage per TC + ByteField('tc0_bw', 0), + ByteField('tc1_bw', 0), + ByteField('tc2_bw', 0), + ByteField('tc3_bw', 0), + ByteField('tc4_bw', 0), + ByteField('tc5_bw', 0), + ByteField('tc6_bw', 0), + ByteField('tc7_bw', 0), + # TSA assignment table: algorithm per TC + ByteEnumField('tc0_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc1_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc2_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc3_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc4_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc5_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc6_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc7_tsa', 0, TSA_ALGORITHMS), + ] + + +class LLDPDUOrgSpecific_IEEE8021_ETS_Recommendation(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: ETS Recommendation. + + Carries the recommended ETS configuration (priority-to-TC mapping, per-TC + bandwidth, and TSA per TC) that the sender would prefer the remote bridge + to apply. Unlike the ETS Configuration TLV this TLV has no flags byte. + + IEEE 802.1Q D.2.9. + + Payload layout (25 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x0A + byte 4 reserved + bytes 5-8 priority assignment table (4 bits per priority, prio 0-7) + bytes 9-16 TC bandwidth table (1 byte per TC, TC 0-7) + bytes 17-24 TSA assignment table (1 byte per TC, TC 0-7) + """ + + SUBTYPE = 0x0A + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 25 + + TSA_ALGORITHMS = _LLDP_ETS_TSA_ALGORITHMS + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitField('_length', 25, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x0A), + ByteField('reserved', 0), + # priority assignment table: 4 bits per priority, MSB=prio0 + BitField('prio0_tc', 0, 4), + BitField('prio1_tc', 0, 4), + BitField('prio2_tc', 0, 4), + BitField('prio3_tc', 0, 4), + BitField('prio4_tc', 0, 4), + BitField('prio5_tc', 0, 4), + BitField('prio6_tc', 0, 4), + BitField('prio7_tc', 0, 4), + # TC bandwidth table: percentage per TC + ByteField('tc0_bw', 0), + ByteField('tc1_bw', 0), + ByteField('tc2_bw', 0), + ByteField('tc3_bw', 0), + ByteField('tc4_bw', 0), + ByteField('tc5_bw', 0), + ByteField('tc6_bw', 0), + ByteField('tc7_bw', 0), + # TSA assignment table: algorithm per TC + ByteEnumField('tc0_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc1_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc2_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc3_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc4_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc5_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc6_tsa', 0, TSA_ALGORITHMS), + ByteEnumField('tc7_tsa', 0, TSA_ALGORITHMS), + ] + + +class LLDPDUOrgSpecific_IEEE8021_PFC_Configuration(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: PFC Configuration. + + Advertises the Priority-based Flow Control (PFC) configuration for the port: + willingness to negotiate, MACsec bypass capability, PFC cap (number of TCs + that can simultaneously have PFC enabled), and the per-priority PFC enable bitmap. + + IEEE 802.1Q D.2.10. + + Payload layout (6 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x0B + byte 4 flags: bit 7: willing, bit 6: MBC, + bits 5-4: reserved, bits 3-0: PFC cap + byte 5 PFC enable bitmap (bit 0: prio 0, ..., bit 7: prio 7) + """ + + PFC_ENABLE_BITS = { + (1 << 0): 'prio0', (1 << 1): 'prio1', + (1 << 2): 'prio2', (1 << 3): 'prio3', + (1 << 4): 'prio4', (1 << 5): 'prio5', + (1 << 6): 'prio6', (1 << 7): 'prio7', + } + + SUBTYPE = 0x0B + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 6 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitField('_length', 6, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x0B), + # flags byte: bit7=willing, bit6=MBC, bits5-4=reserved, bits3-0=PFC cap + BitField('willing', 0, 1), + BitField('mbc', 0, 1), + BitField('reserved', 0, 2), + BitField('pfc_cap', 0, 4), + FlagsField('pfc_enable', 0, 8, PFC_ENABLE_BITS), + ] + + +class LLDPDUOrgSpecific_IEEE8021_AppPriority_Entry(Packet): + """ + Single application priority entry in an Application Priority TLV. + + IEEE 802.1Q D.2.11. + + Payload layout (3 bytes): + byte 0 bits 7-5: priority (3 bits) + bits 4-3: reserved (2 bits) + bits 2-0: sel (3 bits) + bytes 1-2 protocol (big-endian) + """ + + fields_desc = [ + BitField('priority', 0, 3), + BitField('reserved', 0, 2), + BitEnumField('sel', 0, 3, _LLDP_SEL_FIELD_VALUES), + ShortField('protocol', 0), + ] + + def extract_padding(self, s): + return b'', s + + +class LLDPDUOrgSpecific_IEEE8021_Application_Priority( + LLDPDUGenericOrganisationSpecific +): + """ + IEEE 802.1 organizationally specific TLV: Application Priority. + + Maps application protocols (identified by Ethertype, TCP/UDP port, etc.) + to 802.1p priority values. Zero or more 3-byte application entries may + appear in a single TLV. + + IEEE 802.1Q D.2.11. + + Payload layout (5 + 3N bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x0C + byte 4 reserved + N x 3 bytes application entries, each: + byte 0 bits 7-5: priority (3 bits) + bits 4-3: reserved (2 bits) + bits 2-0: sel (3 bits) + bytes 1-2 protocol (big-endian) + """ + + SUBTYPE = 0x0C + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='app_priority_table', + adjust=lambda pkt, x: x + 5), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x0C), + ByteField('reserved', 0), + PacketListField('app_priority_table', [], + LLDPDUOrgSpecific_IEEE8021_AppPriority_Entry, + length_from=lambda pkt: 0 if pkt._length is None + else pkt._length - 5), + ] + + def _check(self): + if conf.contribs['LLDP'].strict_mode() and self._length is not None: + if self._length < 5 or (self._length - 5) % 3 != 0: + raise LLDPInvalidLengthField( + 'IEEE 802.1 Application Priority TLV length must be ' + '5 + 3N (got {})'.format(self._length)) + + +class LLDPDUOrgSpecific_IEEE8021_EVB(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: EVB (Edge Virtual Bridging). + + Negotiates EVB capabilities between a station (hypervisor) and bridge. + + IEEE 802.1Q D.2.12. + + Payload layout (9 bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x0D + byte 4 EVB bridge status + bits 7-3: reserved + bit 2: BGID + bit 1: RRCAP + bit 0: RRCTR + byte 5 EVB station status + bits 7-4: reserved + bit 3: SGID + bit 2: RRREQ + bits 1-0: RRSTAT + byte 6 bits 7-5: R (ECP max retries) + bits 4-0: RTE + byte 7 bits 7-6: EVB mode + bit 5: rwd_rol (ROL for resource wait delay) + bits 4-0: RWD + byte 8 bits 7-6: rka_reserved + bit 5: rka_rol (ROL for reinit keep alive) + bits 4-0: RKA + """ + + SUBTYPE = 0x0D + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + EXPECTED_LENGTH = 9 + + EVB_MODES = { + 0: 'Not Supported', + 1: 'EVB Bridge', + 2: 'EVB station', + 3: 'NVO3', + } + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitField('_length', 9, 9), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x0D), + # EVB bridge status (byte 4) + BitField('bridge_reserved', 0, 5), + BitField('bgid', 0, 1), + BitField('rrcap', 0, 1), + BitField('rrctr', 0, 1), + # EVB station status (byte 5) + BitField('station_reserved', 0, 4), + BitField('sgid', 0, 1), + BitField('rrreq', 0, 1), + BitField('rrstat', 0, 2), + # R (ECP max retries) and RTE (retransmit timer exponent) + BitField('r', 0, 3), + BitField('rte', 0, 5), + # Byte 7: EVB mode, ROL for RWD, resource wait delay + BitEnumField('evb_mode', 0, 2, EVB_MODES), + BitField('rwd_rol', 0, 1), + BitField('rwd', 0, 5), + # Byte 8: reserved, ROL for RKA, reinit keep alive + BitField('rka_reserved', 0, 2), + BitField('rka_rol', 0, 1), + BitField('rka', 0, 5), + ] + + +class LLDPDUOrgSpecific_IEEE8021_SCID_SVID_Pair(Packet): + """ + Single S-channel entry in a CDCP TLV. + + IEEE 802.1Q D.2.13. + + Payload layout (3 bytes): + bits 23-12: scid (S-channel identifier, 12 bits) + bits 11-0: svid (S-channel VLAN identifier, 12 bits) + """ + + fields_desc = [ + BitField('scid', 0, 12), + BitField('svid', 0, 12), + ] + + def extract_padding(self, s): + return b'', s + + +class LLDPDUOrgSpecific_IEEE8021_CDCP(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: CDCP + (Channel Discovery and Configuration Protocol). + + Advertises S-channel assignments between EVB stations and bridges. + + IEEE 802.1Q D.2.13. + + Payload layout (8 + 3N bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x0E + byte 4 bit 7: role (0=EVB station, 1=EVB bridge) + bits 6-4: res1 + bit 3: scomp (short channel compression) + bits 2-0: res2[14:12] + byte 5 bits 7-0: res2[11:4] + byte 6 bits 7-4: res2[3:0] + bits 3-0: chncap[11:8] + byte 7 bits 7-0: chncap[7:0] + bytes 8..N N x 3-octet SCID/SVID entries + """ + + ROLE_VALUES = { + 0: 'EVB station', + 1: 'EVB bridge', + } + + SUBTYPE = 0x0E + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='scid_svid_list', + adjust=lambda pkt, x: x + 8), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x0E), + # Byte 4 + BitEnumField('role', 0, 1, ROLE_VALUES), + BitField('res1', 0, 3), + BitField('scomp', 0, 1), + # res2: 15 bits spanning bytes 4-6 + BitField('res2', 0, 15), + # chncap: 12 bits spanning bytes 6-7 + BitField('chncap', 0, 12), + # N x 3-octet S-channel entries + PacketListField('scid_svid_list', [], + LLDPDUOrgSpecific_IEEE8021_SCID_SVID_Pair, + length_from=lambda pkt: 0 if pkt._length is None + else pkt._length - 8), + ] + + def _check(self): + if (conf.contribs['LLDP'].strict_mode() + and self._length is not None + and self._length < 8): + raise LLDPInvalidLengthField( + 'IEEE 802.1 CDCP TLV length must be >= 8, ' + 'got {}'.format(self._length)) + + +class LLDPDUOrgSpecific_IEEE8021_AppVLANEntry(Packet): + """ + Single entry in an Application VLAN TLV. + + IEEE 802.1Q D.2.14, Table D-12. + + Payload layout (4 bytes): + bytes 0-1 bits 15-4: vid (VLAN ID, 12 bits) + bit 3: reserved + bits 2-0: sel (protocol ID type, 3 bits) + bytes 2-3 protocol (meaning determined by sel, big-endian) + """ + + fields_desc = [ + BitField('vid', 0, 12), + BitField('reserved', 0, 1), + BitEnumField('sel', 0, 3, _LLDP_SEL_FIELD_VALUES), + ShortField('protocol', 0), + ] + + def extract_padding(self, s): + return b'', s + + +class LLDPDUOrgSpecific_IEEE8021_Application_VLAN(LLDPDUGenericOrganisationSpecific): + """ + IEEE 802.1 organizationally specific TLV: Application VLAN. + + Advertises the local Application VLAN Table, mapping protocols to VLAN IDs, + to indicate local configuration to peer stations. + + IEEE 802.1Q D.2.14. + + Payload layout (4 + 4N bytes): + bytes 0-2 OUI 0x0080c2 + byte 3 subtype 0x10 + N x 4 bytes Application VLAN Table entries: + bytes 0-1 bits 15-4: vid (12 bits) + bit 3: reserved + bits 2-0: sel (3 bits) + bytes 2-3 protocol (big-endian) + """ + + SUBTYPE = 0x10 + ORG_CODE = ORG_UNIQUE_CODE_IEEE_802_1 + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='app_vlan_table', + adjust=lambda pkt, x: x + 4), + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_1), + ByteField('subtype', 0x10), + PacketListField('app_vlan_table', [], + LLDPDUOrgSpecific_IEEE8021_AppVLANEntry, + length_from=lambda pkt: 0 if pkt._length is None + else pkt._length - 4), + ] + + def _check(self): + if conf.contribs['LLDP'].strict_mode() and self._length is not None: + if self._length < 4 or (self._length - 4) % 4 != 0: + raise LLDPInvalidLengthField( + 'IEEE 802.1 Application VLAN TLV length must be 4 + 4N, ' + 'got {}'.format(self._length)) class LLDPDUPowerViaMDI(LLDPDUGenericOrganisationSpecific): @@ -750,7 +1577,7 @@ class LLDPDUPowerViaMDI(LLDPDUGenericOrganisationSpecific): fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), BitField('_length', 7, 9), - ThreeBytesField('org_code', LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3), # noqa: E501 + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_3), ByteField('subtype', 2), FlagsField('MDI_power_support', 0, 8, MDI_POWER_SUPPORT), ByteEnumField('PSE_power_pair', 1, PSE_POWER_PAIR), @@ -762,9 +1589,9 @@ def _match_organization_specific(payload): """ match organization specific TLV """ - return (orb(payload[5]) == 2 and orb(payload[1]) == 7 - and bytes_int(payload[2:5]) == - LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3) + return (payload[5] == 2 and _lldp_tlv_length(payload) == 7 + and int.from_bytes(payload[2:5], 'big') == + ORG_UNIQUE_CODE_IEEE_802_3) def _check(self): """ @@ -830,7 +1657,7 @@ class LLDPDUPowerViaMDIDDL(LLDPDUPowerViaMDI): fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), BitField('_length', 12, 9), - ThreeBytesField('org_code', LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3), # noqa: E501 + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_3), ByteField('subtype', 2), FlagsField('MDI_power_support', 0, 8, LLDPDUPowerViaMDI.MDI_POWER_SUPPORT), ByteEnumField('PSE_power_pair', 1, LLDPDUPowerViaMDI.PSE_POWER_PAIR), @@ -861,9 +1688,9 @@ def _match_organization_specific(payload): """ match organization specific TLV """ - return (orb(payload[5]) == 2 and orb(payload[1]) == 12 - and bytes_int(payload[2:5]) == - LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3) + return (payload[5] == 2 and _lldp_tlv_length(payload) == 12 + and int.from_bytes(payload[2:5], 'big') == + ORG_UNIQUE_CODE_IEEE_802_3) def _check(self): """ @@ -987,19 +1814,32 @@ class LLDPDUPowerViaMDIType34(LLDPDUPowerViaMDIDDL): fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), BitField('_length', 29, 9), - ThreeBytesField('org_code', LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3), # noqa: E501 + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_3), ByteField('subtype', 2), FlagsField('MDI_power_support', 0, 8, LLDPDUPowerViaMDI.MDI_POWER_SUPPORT), ByteEnumField('PSE_power_pair', 1, LLDPDUPowerViaMDI.PSE_POWER_PAIR), ByteEnumField('power_class', 1, LLDPDUPowerViaMDI.POWER_CLASS), BitEnumField('power_type_no', 1, 1, LLDPDUPowerViaMDIDDL.POWER_TYPE_NO), BitEnumField('power_type_dir', 1, 1, LLDPDUPowerViaMDIDDL.POWER_TYPE_DIR), - MultipleTypeField([ - ( - BitEnumField('power_source', 0b01, 2, LLDPDUPowerViaMDIDDL.POWER_SOURCE_PD), # noqa: E501 - lambda pkt: pkt.power_type_dir == 1 - ), - ], BitEnumField('power_source', 0b01, 2, LLDPDUPowerViaMDIDDL.POWER_SOURCE_PSE)), # noqa: E501 + MultipleTypeField( + [ + ( + BitEnumField( + 'power_source', + 0b01, + 2, + LLDPDUPowerViaMDIDDL.POWER_SOURCE_PD + ), + lambda pkt: pkt.power_type_dir == 1 + ) + ], + BitEnumField( + 'power_source', + 0b01, + 2, + LLDPDUPowerViaMDIDDL.POWER_SOURCE_PSE + ) + ), MultipleTypeField([ ( BitEnumField('PD_4PID', 0, 2, LLDPDUPowerViaMDIDDL.PD_4PID_SUP), @@ -1041,9 +1881,9 @@ def _match_organization_specific(payload): ''' match organization specific TLV ''' - return (orb(payload[5]) == 2 and orb(payload[1]) == 29 - and bytes_int(payload[2:5]) == - LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3) + return (payload[5] == 2 and _lldp_tlv_length(payload) == 29 + and int.from_bytes(payload[2:5], 'big') == + ORG_UNIQUE_CODE_IEEE_802_3) def _check(self): """ @@ -1132,7 +1972,7 @@ def _decode_ppi(val): fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), BitField('_length', 26, 9), - ThreeBytesField('org_code', LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3), # noqa: E501 + OUIField('org_code', ORG_UNIQUE_CODE_IEEE_802_3), ByteField('subtype', 8), FlagsField('support', 0, 4, MEASURE_TYPE), BitEnumField('source', 0, 4, MEASURE_SOURCE), @@ -1176,9 +2016,9 @@ def _match_organization_specific(payload): ''' match organization specific TLV ''' - return (orb(payload[5]) == 8 and orb(payload[1]) == 26 - and bytes_int(payload[2:5]) == - LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3) + return (payload[5] == 8 and _lldp_tlv_length(payload) == 26 + and int.from_bytes(payload[2:5], 'big') == + ORG_UNIQUE_CODE_IEEE_802_3) def _check(self): """ @@ -1234,6 +2074,21 @@ def _check(self): 0x07: LLDPDUSystemCapabilities, 0x08: LLDPDUManagementAddress, 127: [ + LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID, + LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID, + LLDPDUOrgSpecific_IEEE8021_VLAN_Name, + LLDPDUOrgSpecific_IEEE8021_Protocol_Identity, + LLDPDUOrgSpecific_IEEE8021_VID_Usage_Digest, + LLDPDUOrgSpecific_IEEE8021_Management_VID, + LLDPDUOrgSpecific_IEEE8021_Link_Aggregation, + LLDPDUOrgSpecific_IEEE8021_Congestion_Notification, + LLDPDUOrgSpecific_IEEE8021_ETS_Configuration, + LLDPDUOrgSpecific_IEEE8021_ETS_Recommendation, + LLDPDUOrgSpecific_IEEE8021_PFC_Configuration, + LLDPDUOrgSpecific_IEEE8021_Application_Priority, + LLDPDUOrgSpecific_IEEE8021_EVB, + LLDPDUOrgSpecific_IEEE8021_CDCP, + LLDPDUOrgSpecific_IEEE8021_Application_VLAN, LLDPDUPowerViaMDI, LLDPDUPowerViaMDIDDL, LLDPDUPowerViaMDIType34, diff --git a/test/contrib/lldp.uts b/test/contrib/lldp.uts index bc9ed43b1d9..83c165eadaf 100644 --- a/test/contrib/lldp.uts +++ b/test/contrib/lldp.uts @@ -315,34 +315,13 @@ assert frm[LLDPDUPortDescription].description == b'always!' + organisation specific layers -= ThreeBytesEnumField tests - -three_b_enum_field = ThreeBytesEnumField('test', 0x00, - { - 0x0e: 'fourteen', - 0x00: 'zero', - 0x5566: 'five-six', - 0x0e0000: 'fourteen-zero-zero', - 0x0e0100: 'fourteen-one-zero', - 0x112233: '1#2#3' - }) - -assert three_b_enum_field.i2repr(None, 0) == 'zero' -assert three_b_enum_field.i2repr(None, 0x0e) == 'fourteen' -assert three_b_enum_field.i2repr(None, 0x5566) == 'five-six' -assert three_b_enum_field.i2repr(None, 0x112233) == '1#2#3' -assert three_b_enum_field.i2repr(None, 0x0e0000) == 'fourteen-zero-zero' -assert three_b_enum_field.i2repr(None, 0x0e0100) == 'fourteen-one-zero' -assert three_b_enum_field.i2repr(None, 0x01) == '1' -assert three_b_enum_field.i2repr(None, 0x49763) == '300899' - = LLDPDUGenericOrganisationSpecific tests 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()/\ - LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + LLDPDUGenericOrganisationSpecific(org_code=ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ @@ -353,7 +332,7 @@ frm = Ether(frm) org_spec_layer = frm[LLDPDUGenericOrganisationSpecific] assert org_spec_layer assert org_spec_layer._type == 127 -assert org_spec_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO +assert org_spec_layer.org_code == ORG_UNIQUE_CODE_PNO assert org_spec_layer.subtype == 0x42 assert org_spec_layer._length == 34 @@ -392,7 +371,7 @@ 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()/\ - LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + LLDPDUGenericOrganisationSpecific(org_code=ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ @@ -408,7 +387,7 @@ poe_layer = frm[LLDPDUPowerViaMDI] assert poe_layer assert poe_layer._type == 127 assert int(check_tshark(frm, "lldp", "lldp.tlv.type").split(',')[-2], 0) == 127 -assert poe_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3 +assert poe_layer.org_code == ORG_UNIQUE_CODE_IEEE_802_3 assert int(check_tshark(frm, "lldp", "lldp.orgtlv.oui").split(',')[-1], 0) == 4623 assert poe_layer.subtype == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.subtype"), 0) == 0x02 @@ -426,7 +405,7 @@ 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()/\ - LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + LLDPDUGenericOrganisationSpecific(org_code=ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 ) @@ -445,7 +424,7 @@ 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()/\ - LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + LLDPDUGenericOrganisationSpecific(org_code=ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ @@ -466,7 +445,7 @@ poe_layer = frm[LLDPDUPowerViaMDIDDL] assert poe_layer assert poe_layer._type == 127 assert int(check_tshark(frm, "lldp", "lldp.tlv.type").split(',')[-2], 0) == 127 -assert poe_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3 +assert poe_layer.org_code == ORG_UNIQUE_CODE_IEEE_802_3 assert int(check_tshark(frm, "lldp", "lldp.orgtlv.oui").split(',')[-1], 0) == 4623 assert poe_layer.subtype == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.subtype"), 0) == 0x02 @@ -496,7 +475,7 @@ 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()/\ - LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + LLDPDUGenericOrganisationSpecific(org_code=ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 ) @@ -532,7 +511,7 @@ 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()/\ - LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + LLDPDUGenericOrganisationSpecific(org_code=ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ @@ -570,7 +549,7 @@ poe_layer = frm[LLDPDUPowerViaMDIType34] assert poe_layer assert poe_layer._type == 127 assert int(check_tshark(frm, "lldp", "lldp.tlv.type").split(',')[-2], 0) == 127 -assert poe_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3 +assert poe_layer.org_code == ORG_UNIQUE_CODE_IEEE_802_3 assert int(check_tshark(frm, "lldp", "lldp.orgtlv.oui").split(',')[-1], 0) == 4623 assert poe_layer.subtype == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.subtype"), 0) == 0x02 @@ -632,7 +611,7 @@ 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()/\ - LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + LLDPDUGenericOrganisationSpecific(org_code=ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 ) @@ -719,7 +698,7 @@ 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()/\ - LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + LLDPDUGenericOrganisationSpecific(org_code=ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ @@ -747,7 +726,7 @@ poe_layer_raw = raw(poe_layer) assert poe_layer assert poe_layer._type == 127 -assert poe_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3 +assert poe_layer.org_code == ORG_UNIQUE_CODE_IEEE_802_3 assert poe_layer.subtype == 8 assert poe_layer._length == 26 assert poe_layer.support == 0b0110 @@ -776,7 +755,7 @@ 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()/\ - LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + LLDPDUGenericOrganisationSpecific(org_code=ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 ) @@ -857,3 +836,907 @@ try: assert False except LLDPInvalidFieldValue: pass + ++ IEEE 802.1 org-specific TLVs (PVID and VLAN Name) + += PVID: build produces correct wire bytes + +frm_pvid = Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / \ + LLDPDUChassisID(subtype=4, id=b'router') / \ + LLDPDUPortID(subtype=5, id=b'eth0') / \ + LLDPDUTimeToLive(ttl=120) / \ + LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID(pvid=100) / \ + LLDPDUEndOfLLDPDU() +raw_frm = bytes(frm_pvid) +pvid_start = 34 +assert raw_frm[pvid_start:pvid_start + 8] == bytes.fromhex('fe060080c2010064'), \ + 'PVID wire bytes: {}'.format(raw_frm[pvid_start:pvid_start + 8].hex()) + += PVID: dissect recovers pvid field + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID(pvid=100) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID), 'PVID TLV not dissected' +assert p[LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID].pvid == 100 +assert p[LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID]._length == 6 + += PVID: boundary values (pvid=0 and pvid=4094) build without error + +conf.contribs['LLDP'].strict_mode_disable() +for boundary in (0, 4094): + raw_b = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID(pvid=boundary) / + LLDPDUEndOfLLDPDU()) + assert Ether(raw_b)[LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID].pvid == boundary + += PVID: strict mode rejects wrong TLV length + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID(pvid=1) +tlv_obj._length = 5 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + += PVID: not matched as generic LLDPDUGenericOrganisationSpecific + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID(pvid=77) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) + +pvid_layer = p.getlayer(LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID) +assert pvid_layer is not None, 'LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID layer not found' +assert not p.haslayer(LLDPDUGenericOrganisationSpecific), \ + 'should not be dissected as plain LLDPDUGenericOrganisationSpecific' + += VLAN Name: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_VLAN_Name(vlan_id=200, vlan_name=b'mgmt') / + LLDPDUEndOfLLDPDU()) +vn_start = 34 +assert raw_frm[vn_start:vn_start + 13] == bytes.fromhex('fe0b0080c20300c8046d676d74'), \ + 'VLANName wire bytes: {}'.format(raw_frm[vn_start:vn_start + 13].hex()) + += VLAN Name: dissect recovers vlan_id and vlan_name + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_VLAN_Name(vlan_id=200, vlan_name=b'mgmt') / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_VLAN_Name), 'VLANName TLV not dissected' +assert p[LLDPDUOrgSpecific_IEEE8021_VLAN_Name].vlan_id == 200 +assert p[LLDPDUOrgSpecific_IEEE8021_VLAN_Name].vlan_name == b'mgmt' +assert p[LLDPDUOrgSpecific_IEEE8021_VLAN_Name]._length == 11 +assert p[LLDPDUOrgSpecific_IEEE8021_VLAN_Name].vlan_name_length == 4 + += VLAN Name: empty vlan_name (length field = 7) + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_VLAN_Name(vlan_id=1, vlan_name=b'') / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +tlv = p[LLDPDUOrgSpecific_IEEE8021_VLAN_Name] +assert tlv._length == 7 +assert tlv.vlan_name == b'' +assert tlv.vlan_name_length == 0 + += VLAN Name: strict mode rejects TLV length < 7 + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_VLAN_Name(vlan_id=1, vlan_name=b'x') +tlv_obj._length = 6 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +tlv_obj._length = None +tlv_obj._check() +conf.contribs['LLDP'].strict_mode_disable() + += VLAN Name: build and dissect + +tlv = LLDPDUOrgSpecific_IEEE8021_VLAN_Name(vlan_id=200, vlan_name=b'mgmt') +assert bytes(tlv) == bytes(LLDPDUOrgSpecific_IEEE8021_VLAN_Name(bytes(tlv))), \ + 'VLAN Name build and dissect failed' + += multiple TLVs in one frame: PVID + two VLAN Name TLVs + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID(pvid=10) / + LLDPDUOrgSpecific_IEEE8021_VLAN_Name(vlan_id=10, vlan_name=b'data') / + LLDPDUOrgSpecific_IEEE8021_VLAN_Name(vlan_id=20, vlan_name=b'voice') / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID) +assert p[LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID].pvid == 10 +vn1 = p.getlayer(LLDPDUOrgSpecific_IEEE8021_VLAN_Name, nb=1) +vn2 = p.getlayer(LLDPDUOrgSpecific_IEEE8021_VLAN_Name, nb=2) +assert vn1 is not None and vn2 is not None, 'expected 2 VLANName TLVs' +assert vn1.vlan_id == 10 and vn1.vlan_name == b'data' +assert vn2.vlan_id == 20 and vn2.vlan_name == b'voice' + += VLAN Name: strict mode rejects duplicate VID+name pair + +conf.contribs['LLDP'].strict_mode_enable() +pkt = (Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_VLAN_Name(vlan_id=10, vlan_name=b'data') / + LLDPDUOrgSpecific_IEEE8021_VLAN_Name(vlan_id=10, vlan_name=b'data') / + LLDPDUEndOfLLDPDU()) +try: + bytes(pkt) + assert False, 'should have raised LLDPInvalidFrameStructure' +except LLDPInvalidFrameStructure: + pass + +conf.contribs['LLDP'].strict_mode_disable() + ++ IEEE 802.1 org-specific TLVs (subtypes 2, 4, 5, 6, 7) + += PPVID: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID(ppvid_supported=1, ppvid_enabled=1, ppvid=10) / + LLDPDUEndOfLLDPDU()) +ppvid_start = 34 +assert raw_frm[ppvid_start:ppvid_start + 9] == bytes.fromhex('fe070080c20206000a'), \ + 'PPVID wire bytes: {}'.format(raw_frm[ppvid_start:ppvid_start + 9].hex()) + += PPVID: dissect recovers flags and ppvid + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID(ppvid_supported=1, ppvid_enabled=1, ppvid=10) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID), 'PPVID TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID] +assert tlv.ppvid == 10 +assert tlv.ppvid_supported == 1 +assert tlv.ppvid_enabled == 1 +assert tlv._length == 7 + += PPVID: strict mode rejects wrong TLV length + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID(ppvid=1) +tlv_obj._length = 6 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + += PPVID: individual flag bits map to correct wire positions + +tlv = LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID(ppvid_enabled=1, ppvid_supported=0, ppvid=0) +flags_byte = bytes(tlv)[6] +assert flags_byte == 0x04, \ + 'ppvid_enabled=1 alone must set bit 2 (0x04), got 0x{:02x}'.format(flags_byte) + +tlv = LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID(ppvid_enabled=0, ppvid_supported=1, ppvid=0) +flags_byte = bytes(tlv)[6] +assert flags_byte == 0x02, \ + 'ppvid_supported=1 alone must set bit 1 (0x02), got 0x{:02x}'.format(flags_byte) + +tlv = LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID(bytes.fromhex('fe070080c20204000a')) +assert tlv.ppvid_enabled == 1 and tlv.ppvid_supported == 0, \ + 'flags byte 0x04 must dissect as ppvid_enabled=1, ppvid_supported=0' + +tlv = LLDPDUOrgSpecific_IEEE8021_Port_And_Protocol_VLAN_ID(bytes.fromhex('fe070080c20202000a')) +assert tlv.ppvid_enabled == 0 and tlv.ppvid_supported == 1, \ + 'flags byte 0x02 must dissect as ppvid_enabled=0, ppvid_supported=1' + += Protocol Identity: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Protocol_Identity(protocol_identity=b'\xaa\xbb\xcc') / + LLDPDUEndOfLLDPDU()) +pi_start = 34 +assert raw_frm[pi_start:pi_start + 10] == bytes.fromhex('fe080080c20403aabbcc'), \ + 'ProtocolIdentity wire bytes: {}'.format(raw_frm[pi_start:pi_start + 10].hex()) + += Protocol Identity: dissect recovers protocol_identity + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Protocol_Identity(protocol_identity=b'\xaa\xbb\xcc') / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Protocol_Identity), 'Protocol Identity TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_Protocol_Identity] +assert tlv.protocol_identity == b'\xaa\xbb\xcc' +assert tlv.protocol_identity_length == 3 +assert tlv._length == 8 + += Protocol Identity: build and dissect + +tlv = LLDPDUOrgSpecific_IEEE8021_Protocol_Identity(protocol_identity=b'\xaa\xbb\xcc') +assert bytes(tlv) == bytes(LLDPDUOrgSpecific_IEEE8021_Protocol_Identity(bytes(tlv))), \ + 'Protocol Identity build and dissect failed' + += Protocol Identity: strict mode enforces length >= 5 + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_Protocol_Identity(protocol_identity=b'\xaa') +tlv_obj._length = 4 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +tlv_obj._length = 5 +tlv_obj._check() +conf.contribs['LLDP'].strict_mode_disable() + += Protocol Identity: strict mode rejects protocol_identity longer than 255 bytes + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_Protocol_Identity(protocol_identity=b'\xaa' * 256) +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + += VID Usage Digest: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_VID_Usage_Digest(vid_usage_digest=0xdeadbeef) / + LLDPDUEndOfLLDPDU()) +vud_start = 34 +assert raw_frm[vud_start:vud_start + 10] == bytes.fromhex('fe080080c205deadbeef'), \ + 'VIDUsageDigest wire bytes: {}'.format(raw_frm[vud_start:vud_start + 10].hex()) + += VID Usage Digest: dissect recovers vid_usage_digest + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_VID_Usage_Digest(vid_usage_digest=0xdeadbeef) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_VID_Usage_Digest), 'VID Usage Digest TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_VID_Usage_Digest] +assert tlv.vid_usage_digest == 0xdeadbeef +assert tlv._length == 8 + += VID Usage Digest: strict mode rejects length != 8 + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_VID_Usage_Digest() +tlv_obj._length = 9 +try: + tlv_obj._check() + assert False, 'expected LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +tlv_obj._length = 8 +tlv_obj._check() +conf.contribs['LLDP'].strict_mode_disable() + += Management VID: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Management_VID(management_vid=15) / + LLDPDUEndOfLLDPDU()) +mvid_start = 34 +assert raw_frm[mvid_start:mvid_start + 8] == bytes.fromhex('fe060080c206000f'), \ + 'Management VID wire bytes: {}'.format(raw_frm[mvid_start:mvid_start + 8].hex()) + += Management VID: dissect recovers management_vid + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Management_VID(management_vid=15) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Management_VID), 'Management VID TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_Management_VID] +assert tlv.management_vid == 15 +assert tlv._length == 6 + += Link Aggregation: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Link_Aggregation(aggregation_status=0x03, aggregated_port_id=2) / + LLDPDUEndOfLLDPDU()) +la_start = 34 +assert raw_frm[la_start:la_start + 11] == bytes.fromhex('fe090080c2070300000002'), \ + 'LinkAggregation wire bytes: {}'.format(raw_frm[la_start:la_start + 11].hex()) + += Link Aggregation: dissect recovers aggregation_status and aggregated_port_id + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Link_Aggregation(aggregation_status=0x03, aggregated_port_id=2) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Link_Aggregation), 'Link Aggregation TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_Link_Aggregation] +assert int(tlv.aggregation_status) == 0x03 +assert tlv.aggregated_port_id == 2 +assert tlv._length == 9 + ++ IEEE 802.1 org-specific TLVs (subtypes 8, 9, 10, 11, 12) + += Congestion Notification: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Congestion_Notification(cnpv_indicators=0x05, ready_indicators=0x03) / + LLDPDUEndOfLLDPDU()) +cn_start = 34 +assert raw_frm[cn_start:cn_start + 8] == bytes.fromhex('fe060080c2080503'), \ + 'CN wire bytes: {}'.format(raw_frm[cn_start:cn_start + 8].hex()) + += Congestion Notification: dissect recovers cnpv and ready indicators + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Congestion_Notification(cnpv_indicators=0x05, ready_indicators=0x03) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Congestion_Notification), 'CN TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_Congestion_Notification] +assert int(tlv.cnpv_indicators) == 0x05 +assert int(tlv.ready_indicators) == 0x03 +assert tlv._length == 6 + += Congestion Notification: strict mode rejects wrong TLV length + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_Congestion_Notification() +tlv_obj._length = 7 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + += ETS Configuration: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_ETS_Configuration( + willing=1, cbs=0, max_tcs=0, + prio0_tc=0, prio1_tc=0, prio2_tc=0, prio3_tc=0, + prio4_tc=0, prio5_tc=0, prio6_tc=0, prio7_tc=0, + tc0_bw=100, tc1_bw=0, tc2_bw=0, tc3_bw=0, + tc4_bw=0, tc5_bw=0, tc6_bw=0, tc7_bw=0, + tc0_tsa=2, tc1_tsa=0, tc2_tsa=0, tc3_tsa=0, + tc4_tsa=0, tc5_tsa=0, tc6_tsa=0, tc7_tsa=0) / + LLDPDUEndOfLLDPDU()) +ets_start = 34 +raw = raw_frm[ets_start:ets_start + 27] +assert raw[:6] == bytes.fromhex('fe190080c209'), \ + 'ETS Config header: {}'.format(raw[:6].hex()) +assert raw[6] == 0x80, 'byte 4 (willing bit set): got 0x{:02x}'.format(raw[6]) +assert len(raw) == 27, 'ETS Config total length: {}'.format(len(raw)) + += ETS Configuration: dissect recovers fields + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_ETS_Configuration( + willing=1, max_tcs=4, + prio0_tc=0, prio1_tc=1, prio2_tc=2, prio3_tc=3, + prio4_tc=0, prio5_tc=1, prio6_tc=2, prio7_tc=3, + tc0_bw=25, tc1_bw=25, tc2_bw=25, tc3_bw=25, + tc4_bw=0, tc5_bw=0, tc6_bw=0, tc7_bw=0, + tc0_tsa=2, tc1_tsa=2, tc2_tsa=2, tc3_tsa=2, + tc4_tsa=0, tc5_tsa=0, tc6_tsa=0, tc7_tsa=0) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_ETS_Configuration), 'ETS Config TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_ETS_Configuration] +assert tlv.willing == 1 +assert tlv.max_tcs == 4 +assert tlv.prio1_tc == 1 +assert tlv.tc0_bw == 25 +assert tlv.tc0_tsa == 2 +assert tlv._length == 25 + += ETS Configuration: strict mode rejects wrong TLV length + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_ETS_Configuration() +tlv_obj._length = 24 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + += ETS Recommendation: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_ETS_Recommendation( + prio0_tc=0, prio1_tc=1, prio2_tc=2, prio3_tc=3, + prio4_tc=0, prio5_tc=1, prio6_tc=2, prio7_tc=3, + tc0_bw=25, tc1_bw=25, tc2_bw=25, tc3_bw=25, + tc4_bw=0, tc5_bw=0, tc6_bw=0, tc7_bw=0, + tc0_tsa=2, tc1_tsa=2, tc2_tsa=2, tc3_tsa=2, + tc4_tsa=0, tc5_tsa=0, tc6_tsa=0, tc7_tsa=0) / + LLDPDUEndOfLLDPDU()) +ets_start = 34 +raw = raw_frm[ets_start:ets_start + 27] +assert raw[:6] == bytes.fromhex('fe190080c20a'), \ + 'ETS Rec header: {}'.format(raw[:6].hex()) +assert raw[6] == 0x00, 'reserved byte must be 0: got 0x{:02x}'.format(raw[6]) +assert len(raw) == 27, 'ETS Rec total length: {}'.format(len(raw)) + += ETS Recommendation: dissect recovers fields + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_ETS_Recommendation( + prio0_tc=0, prio1_tc=1, prio2_tc=0, prio3_tc=1, + prio4_tc=0, prio5_tc=1, prio6_tc=0, prio7_tc=1, + tc0_bw=50, tc1_bw=50, tc2_bw=0, tc3_bw=0, + tc4_bw=0, tc5_bw=0, tc6_bw=0, tc7_bw=0, + tc0_tsa=2, tc1_tsa=2, tc2_tsa=0, tc3_tsa=0, + tc4_tsa=0, tc5_tsa=0, tc6_tsa=0, tc7_tsa=0) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_ETS_Recommendation), 'ETS Rec TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_ETS_Recommendation] +assert tlv.reserved == 0 +assert tlv.prio1_tc == 1 +assert tlv.tc0_bw == 50 +assert tlv.tc0_tsa == 2 +assert tlv._length == 25 + += PFC Configuration: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_PFC_Configuration(willing=1, mbc=0, pfc_cap=8, pfc_enable=0x0f) / + LLDPDUEndOfLLDPDU()) +pfc_start = 34 +assert raw_frm[pfc_start:pfc_start + 8] == bytes.fromhex('fe060080c20b880f'), \ + 'PFC wire bytes: {}'.format(raw_frm[pfc_start:pfc_start + 8].hex()) + += PFC Configuration: dissect recovers fields + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_PFC_Configuration(willing=1, mbc=0, pfc_cap=8, pfc_enable=0x0f) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_PFC_Configuration), 'PFC Config TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_PFC_Configuration] +assert tlv.willing == 1 +assert tlv.mbc == 0 +assert tlv.pfc_cap == 8 +assert int(tlv.pfc_enable) == 0x0f +assert tlv._length == 6 + += PFC Configuration: strict mode rejects wrong TLV length + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_PFC_Configuration() +tlv_obj._length = 7 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + += Application Priority: build produces correct wire bytes + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Application_Priority(app_priority_table=[ + LLDPDUOrgSpecific_IEEE8021_AppPriority_Entry(priority=5, sel=1, protocol=0x8906), + LLDPDUOrgSpecific_IEEE8021_AppPriority_Entry(priority=3, sel=2, protocol=0x0cbc), + ]) / + LLDPDUEndOfLLDPDU() +) +ap_start = 34 +assert raw_frm[ap_start:ap_start + 13] == bytes.fromhex('fe0b0080c20c00a18906620cbc'), \ + 'AppPriority wire bytes: {}'.format(raw_frm[ap_start:ap_start + 13].hex()) + += Application Priority: dissect recovers entries + +raw_frm = bytes(Ether(dst='01:80:c2:00:00:0e', src='aa:bb:cc:dd:ee:ff', type=0x88cc) / + LLDPDUChassisID(subtype=4, id=b'router') / + LLDPDUPortID(subtype=5, id=b'eth0') / + LLDPDUTimeToLive(ttl=120) / + LLDPDUOrgSpecific_IEEE8021_Application_Priority(app_priority_table=[ + LLDPDUOrgSpecific_IEEE8021_AppPriority_Entry(priority=5, sel=1, protocol=0x8906), + LLDPDUOrgSpecific_IEEE8021_AppPriority_Entry(priority=3, sel=2, protocol=0x0cbc), + ]) / + LLDPDUEndOfLLDPDU()) +p = Ether(raw_frm) +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Application_Priority), 'AppPriority TLV not dissected' +tlv = p[LLDPDUOrgSpecific_IEEE8021_Application_Priority] +assert tlv._length == 11 +assert len(tlv.app_priority_table) == 2 +assert tlv.app_priority_table[0].priority == 5 +assert tlv.app_priority_table[0].sel == 1 +assert tlv.app_priority_table[0].protocol == 0x8906 +assert tlv.app_priority_table[1].priority == 3 +assert tlv.app_priority_table[1].sel == 2 +assert tlv.app_priority_table[1].protocol == 0x0cbc + += Application Priority: build and dissect + +tlv = LLDPDUOrgSpecific_IEEE8021_Application_Priority(app_priority_table=[ + LLDPDUOrgSpecific_IEEE8021_AppPriority_Entry(priority=5, sel=1, protocol=0x8906), +]) +assert bytes(tlv) == bytes(LLDPDUOrgSpecific_IEEE8021_Application_Priority(bytes(tlv))), \ + 'AppPriority build and dissect failed' + += Application Priority: strict mode rejects TLV length < 5 + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_Application_Priority() +tlv_obj._length = 4 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + += Application Priority: strict mode rejects length not 5 + 3N + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_Application_Priority() +tlv_obj._length = 7 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +tlv_obj._length = 8 +tlv_obj._check() +conf.contribs['LLDP'].strict_mode_disable() + ++ EVB TLV (IEEE 802.1Q D.2.12) + += EVB TLV: build produces correct wire bytes + +tlv = LLDPDUOrgSpecific_IEEE8021_EVB() +wire = bytes(tlv) +assert wire == bytes.fromhex('fe090080c20d0000000000'), \ + 'EVB default wire bytes: {}'.format(wire.hex()) + += EVB TLV: build with mixed-width bit fields + +tlv = LLDPDUOrgSpecific_IEEE8021_EVB( + bgid=1, rrcap=1, + sgid=1, rrreq=1, rrstat=0b10, + r=3, rte=10, + evb_mode=1, rwd_rol=1, rwd=7, + rka_rol=1, rka=3, +) +wire = bytes(tlv) +assert wire == bytes.fromhex('fe090080c20d060e6a6723'), \ + 'EVB wire bytes: {}'.format(wire.hex()) + += EVB TLV: dissect recovers fields + +tlv = LLDPDUOrgSpecific_IEEE8021_EVB(bytes.fromhex('fe090080c20d060e6a6723')) +assert tlv._length == 9 +assert tlv.bgid == 1 +assert tlv.rrcap == 1 +assert tlv.rrctr == 0 +assert tlv.sgid == 1 +assert tlv.rrreq == 1 +assert tlv.rrstat == 0b10 +assert tlv.r == 3 +assert tlv.rte == 10 +assert tlv.evb_mode == 1 +assert tlv.rwd_rol == 1 +assert tlv.rwd == 7 +assert tlv.rka_rol == 1 +assert tlv.rka == 3 + += EVB TLV: build and dissect + +tlv = LLDPDUOrgSpecific_IEEE8021_EVB(bgid=1, sgid=1, evb_mode=2, rwd=5, rka=3) +assert bytes(tlv) == bytes(LLDPDUOrgSpecific_IEEE8021_EVB(bytes(tlv))), \ + 'EVB build and dissect failed' + += EVB TLV: strict mode rejects length != 9 + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_EVB() +tlv_obj._length = 8 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + += EVB TLV: _match_organization_specific requires length == 9 + +assert LLDPDUOrgSpecific_IEEE8021_EVB._match_organization_specific( + bytes.fromhex('fe090080c20d0000000000')), \ + 'EVB matcher should accept length == 9' +assert not LLDPDUOrgSpecific_IEEE8021_EVB._match_organization_specific( + bytes.fromhex('fe0a0080c20d000000000000')), \ + 'EVB matcher must reject length != 9' + ++ CDCP TLV (IEEE 802.1Q D.2.13) + += CDCP TLV: build default (no entries) + +tlv = LLDPDUOrgSpecific_IEEE8021_CDCP() +wire = bytes(tlv) +assert wire == bytes.fromhex('fe080080c20e00000000'), \ + 'CDCP default wire bytes: {}'.format(wire.hex()) + += CDCP TLV: build with fields and two entries + +tlv = LLDPDUOrgSpecific_IEEE8021_CDCP( + role=1, + scomp=1, + chncap=10, + scid_svid_list=[ + LLDPDUOrgSpecific_IEEE8021_SCID_SVID_Pair(scid=1, svid=2), + LLDPDUOrgSpecific_IEEE8021_SCID_SVID_Pair(scid=0x123, svid=0x456), + ], +) +wire = bytes(tlv) +assert wire == bytes.fromhex('fe0e0080c20e8800000a001002123456'), \ + 'CDCP wire bytes: {}'.format(wire.hex()) + += CDCP TLV: dissect recovers fields and entries + +tlv = LLDPDUOrgSpecific_IEEE8021_CDCP(bytes.fromhex('fe0e0080c20e8800000a001002123456')) +assert tlv._length == 14 +assert tlv.role == 1 +assert tlv.scomp == 1 +assert tlv.chncap == 10 +assert len(tlv.scid_svid_list) == 2 +assert tlv.scid_svid_list[0].scid == 1 +assert tlv.scid_svid_list[0].svid == 2 +assert tlv.scid_svid_list[1].scid == 0x123 +assert tlv.scid_svid_list[1].svid == 0x456 + += CDCP TLV: build and dissect + +tlv = LLDPDUOrgSpecific_IEEE8021_CDCP( + role=1, + chncap=10, + scid_svid_list=[LLDPDUOrgSpecific_IEEE8021_SCID_SVID_Pair(scid=5, svid=100)], +) +assert bytes(tlv) == bytes(LLDPDUOrgSpecific_IEEE8021_CDCP(bytes(tlv))), \ + 'CDCP build and dissect failed' + += CDCP TLV: strict mode rejects length < 8 + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_CDCP() +tlv_obj._length = 7 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + ++ Application VLAN TLV (IEEE 802.1Q D.2.14) + += Application VLAN TLV: build default (no entries) + +tlv = LLDPDUOrgSpecific_IEEE8021_Application_VLAN() +wire = bytes(tlv) +assert wire == bytes.fromhex('fe040080c210'), \ + 'AppVLAN default wire bytes: {}'.format(wire.hex()) + += Application VLAN TLV: build with two entries + +tlv = LLDPDUOrgSpecific_IEEE8021_Application_VLAN(app_vlan_table=[ + LLDPDUOrgSpecific_IEEE8021_AppVLANEntry(vid=100, sel=1, protocol=0x8100), + LLDPDUOrgSpecific_IEEE8021_AppVLANEntry(vid=200, sel=2, protocol=0x0050), +]) +wire = bytes(tlv) +assert wire == bytes.fromhex('fe0c0080c210064181000c820050'), \ + 'AppVLAN wire bytes: {}'.format(wire.hex()) + += Application VLAN TLV: dissect recovers entries + +tlv = LLDPDUOrgSpecific_IEEE8021_Application_VLAN( + bytes.fromhex('fe0c0080c210064181000c820050') +) +assert tlv._length == 12 +assert len(tlv.app_vlan_table) == 2 +assert tlv.app_vlan_table[0].vid == 100 +assert tlv.app_vlan_table[0].sel == 1 +assert tlv.app_vlan_table[0].protocol == 0x8100 +assert tlv.app_vlan_table[1].vid == 200 +assert tlv.app_vlan_table[1].sel == 2 +assert tlv.app_vlan_table[1].protocol == 0x0050 + += Application VLAN TLV: build and dissect + +tlv = LLDPDUOrgSpecific_IEEE8021_Application_VLAN(app_vlan_table=[ + LLDPDUOrgSpecific_IEEE8021_AppVLANEntry(vid=100, sel=1, protocol=0x8100), +]) +assert bytes(tlv) == bytes(LLDPDUOrgSpecific_IEEE8021_Application_VLAN(bytes(tlv))), \ + 'AppVLAN build and dissect failed' + += Application VLAN TLV: strict mode rejects invalid length + +conf.contribs['LLDP'].strict_mode_enable() +tlv_obj = LLDPDUOrgSpecific_IEEE8021_Application_VLAN() +tlv_obj._length = 5 +try: + tlv_obj._check() + assert False, 'should have raised LLDPInvalidLengthField' +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + ++ _lldp_tlv_length helper + += _lldp_tlv_length: correct 9-bit extraction for length >= 256 + +from scapy.contrib.lldp import _lldp_tlv_length +# Craft a raw TLV header where the 9-bit length field is 263 (0x107): +# type=127 (0x7f), length=263 (0x107) +# byte0 = (0x7f << 1) | (263 >> 8) = 0xfe | 0x01 = 0xff +# byte1 = 263 & 0xff = 0x07 +payload_high_bit = bytes([0xff, 0x07]) +assert _lldp_tlv_length(payload_high_bit) == 263, \ + 'expected 263, got {}'.format(_lldp_tlv_length(payload_high_bit)) + +# Sanity check: normal case length=7 (PPVID) +# byte0 = (0x7f << 1) | (7 >> 8) = 0xfe, byte1 = 0x07 +payload_normal = bytes([0xfe, 0x07]) +assert _lldp_tlv_length(payload_normal) == 7 + ++ real-world packet dissection + += Wireshark sample: Summit300-48 + +# Source: https://wiki.wireshark.org/LinkLayerDiscoveryProtocol (look for lldp.detailed.pcap) +raw = bytes.fromhex( + '020704000130f9ada0040405312f31' + '06020078' + '081753756d6d69743330302d34382d506f72742031303031' + '000a0d53756d6d69743330302d3438' + '000c4c53756d6d69743330302d3438202d2056657273696f6e20372e34652e3120284275696c642035292062792052656c656173655f4d61737465722030352f32372f30352030343a35333a3131' + '000e0400140014' + '100e0706000130f9ada002000003e9' + '00fe0700120f02070100' + 'fe0900120f01036c000010' + 'fe0900120f030100000000' + 'fe0600120f0405f2' + 'fe060080c20101e8' + 'fe070080c202010000' + 'fe170080c20301e81076322d303438382d30332d3035303500' + 'fe050080c204000000' +) +p = LLDPDU(raw) + +assert p.haslayer(LLDPDUChassisID) +assert p[LLDPDUChassisID].subtype == 4 # MAC address +assert p[LLDPDUChassisID].id == '00:01:30:f9:ad:a0' + +assert p.haslayer(LLDPDUPortID) +assert p[LLDPDUPortID].id == b'1/1' + +assert p.haslayer(LLDPDUTimeToLive) +assert p[LLDPDUTimeToLive].ttl == 120 + +assert p.haslayer(LLDPDUSystemName) +assert p[LLDPDUSystemName].system_name == b'Summit300-48\x00' + +assert p.haslayer(LLDPDUManagementAddress) +assert p[LLDPDUManagementAddress].interface_number == 1001 + +assert p.haslayer(LLDPDUPowerViaMDI) +assert p[LLDPDUPowerViaMDI].org_code == ORG_UNIQUE_CODE_IEEE_802_3 + +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID) +assert p[LLDPDUOrgSpecific_IEEE8021_Port_VLAN_ID].pvid == 488 + +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_VLAN_Name) +assert p[LLDPDUOrgSpecific_IEEE8021_VLAN_Name].vlan_id == 488 +assert p[LLDPDUOrgSpecific_IEEE8021_VLAN_Name].vlan_name == b'v2-0488-03-0505\x00' + +assert p.haslayer(LLDPDUOrgSpecific_IEEE8021_Protocol_Identity) +assert p[LLDPDUOrgSpecific_IEEE8021_Protocol_Identity].protocol_identity_length == 0 +assert p[LLDPDUOrgSpecific_IEEE8021_Protocol_Identity]._length == 5 + +assert p.haslayer(LLDPDUEndOfLLDPDU) +assert not p.haslayer(Raw), 'unexpected Raw remainder'