Repository: secdev/scapy

Issue:
lldp fails to decode messages containing organisation specific options
affected version: master

- org specific TLV currently not handled 
- remaining payload is not decoded (guess_payload() in LLDPDU needs a proper default - currently it just returns None for TLV-type 127), LLDPDUEndOfLLDPDU is lost
- LLDP struct integrity test catches in (if not disabled = default) and throws an exception

```
Traceback (most recent call last):
  File "send-frames.py", line 28, in <module>
    sniff(iface='eth2', timeout=20, prn=frame_handler)
  File "/tmp/wtf/scapy/sendrecv.py", line 744, in sniff
    p = s.recv()
  File "/tmp/wtf/scapy/arch/linux.py", line 606, in recv
    pkt = cls(pkt)
  File "/tmp/wtf/scapy/base_classes.py", line 240, in __call__
    i.__init__(*args, **kargs)
  File "/tmp/wtf/scapy/packet.py", line 132, in __init__
    self.dissection_done(self)
  File "/tmp/wtf/scapy/packet.py", line 161, in dissection_done
    self.payload.dissection_done(pkt)
  File "/tmp/wtf/scapy/contrib/lldp.py", line 277, in dissection_done
    LLDPDU._frame_structure_check(LLDPDU.LAYER_STACK)
  File "/tmp/wtf/scapy/contrib/lldp.py", line 216, in _frame_structure_check
    ' '.join(standard_frame_structure)))
scapy.contrib.lldp.LLDPInvalidFrameStructure: Invalid frame structure.
got: LLDPDUChassisID LLDPDUPortID LLDPDUTimeToLive LLDPDUPortDescription LLDPDUSystemDescription LLDPDUSystemCapabilities LLDPDUManagementAddress
expected: LLDPDUChassisID LLDPDUPortID LLDPDUTimeToLive <...> LLDPDUEndOfLLDPDU
```

Sample:
[lldp-with-org-specific-types.zip](https://github.com/secdev/scapy/files/1725955/lldp-with-org-specific-types.zip)

Fix is on its way.


Discussion:

Resolving patch:
diff --git a/scapy/contrib/lldp.py b/scapy/contrib/lldp.py
--- a/scapy/contrib/lldp.py
+++ b/scapy/contrib/lldp.py
@@ -28,7 +28,7 @@
             - IEEE 802.1AB 2016 - LLDP protocol, topology and MIB description
 
     :TODO:
-        - organization specific TLV e.g. ProfiNet
+        - 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
@@ -45,11 +45,11 @@
 from scapy.layers.dot11 import Packet
 from scapy.layers.l2 import Ether, Dot1Q, bind_layers, \
     BitField, StrLenField, ByteEnumField, BitEnumField, \
-    BitFieldLenField, ShortField, Padding, Scapy_Exception, \
-    XStrLenField
+    BitFieldLenField, ShortField, Scapy_Exception, \
+    XStrLenField, ByteField, EnumField, ThreeBytesField
 from scapy.modules.six.moves import range
 from scapy.data import ETHER_TYPES
-from scapy.compat import orb, raw
+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'
@@ -123,8 +123,11 @@ class LLDPDU(Packet):
 
     def guess_payload_class(self, payload):
         # type is a 7-bit bitfield spanning bits 1..7 -> div 2
-        lldpdu_tlv_type = orb(payload[0]) // 2
-        return LLDPDU_CLASS_TYPES[lldpdu_tlv_type]
+        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):
@@ -664,6 +667,38 @@ def do_build(self):
         self._check()
         return super(LLDPDUManagementAddress, self).do_build()
 
+
+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,
@@ -674,8 +709,7 @@ def do_build(self):
     0x06: LLDPDUSystemDescription,
     0x07: LLDPDUSystemCapabilities,
     0x08: LLDPDUManagementAddress,
-    range(0x09, 0x7e): None,  # reserved - future standardization
-    127: None  # organisation specific TLV
+    127: LLDPDUGenericOrganisationSpecific
 }
 
 class LLDPConfiguration(object):
diff --git a/scapy/fields.py b/scapy/fields.py
--- a/scapy/fields.py
+++ b/scapy/fields.py
@@ -1049,7 +1049,6 @@ def i2repr_one(self, pkt, x):
                     return ret
         return lhex(x)
 
-
 class _MultiEnumField(_EnumField):
     def __init__(self, name, default, enum, depends_on, fmt = "H"):
 


Test patch: