From 3935d91f73eafd5a97f65da1cbb715f5db9526b7 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Fri, 9 Mar 2018 20:36:47 +0100 Subject: [PATCH] LLDP: support complex ID types --- scapy/contrib/lldp.py | 121 +++++++++++++++++++++-------------------- scapy/contrib/lldp.uts | 21 ++++--- scapy/utils.py | 2 +- 3 files changed, 78 insertions(+), 66 deletions(-) diff --git a/scapy/contrib/lldp.py b/scapy/contrib/lldp.py index eeab0e9f3..99550193e 100644 --- a/scapy/contrib/lldp.py +++ b/scapy/contrib/lldp.py @@ -42,11 +42,13 @@ """ from scapy.config import conf -from scapy.layers.dot11 import Packet -from scapy.layers.l2 import Ether, Dot1Q, bind_layers, \ - BitField, StrLenField, ByteEnumField, BitEnumField, \ - BitFieldLenField, ShortField, Scapy_Exception, \ - XStrLenField, ByteField, EnumField, ThreeBytesField +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 @@ -282,6 +284,48 @@ class LLDPDU(Packet): 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): """ @@ -299,6 +343,11 @@ class LLDPDUChassisID(LLDPDU): 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 @@ -311,9 +360,9 @@ class LLDPDUChassisID(LLDPDU): fields_desc = [ BitEnumField('_type', 0x01, 7, LLDPDU.TYPES), BitFieldLenField('_length', None, 9, length_of='id', - adjust=lambda pkt, x: len(pkt.id) + 1), + adjust=lambda pkt, x: _ldp_id_adjustlen(pkt, x)), ByteEnumField('subtype', 0x00, LLDP_CHASSIS_ID_TLV_SUBTYPES), - XStrLenField('id', '', length_from=lambda pkt: pkt._length - 1) + _LLDPidField('id', '', LLDP_CHASSIS_ID_TLV_SUBTYPES_FIELDS, length_from=lambda pkt: pkt._length - 1) ] def _check(self): @@ -323,14 +372,6 @@ class LLDPDUChassisID(LLDPDU): if conf.contribs['LLDP'].strict_mode() and not self.id: raise LLDPInvalidLengthField('id must be >= 1 characters long') - def post_dissect(self, s): - self._check() - return super(LLDPDUChassisID, self).post_dissect(s) - - def do_build(self): - self._check() - return super(LLDPDUChassisID, self).do_build() - class LLDPDUPortID(LLDPDU): """ @@ -348,6 +389,11 @@ class LLDPDUPortID(LLDPDU): range(0x08, 0xff): 'reserved' } + LLDP_PORT_ID_TLV_SUBTYPES = { + 0x03: MACField, + 0x04: IPField, + } + SUBTYPE_RESERVED = 0x00 SUBTYPE_INTERFACE_ALIAS = 0x01 SUBTYPE_PORT_COMPONENT = 0x02 @@ -360,9 +406,9 @@ class LLDPDUPortID(LLDPDU): fields_desc = [ BitEnumField('_type', 0x02, 7, LLDPDU.TYPES), BitFieldLenField('_length', None, 9, length_of='id', - adjust=lambda pkt, x: len(pkt.id) + 1), + adjust=lambda pkt, x: _ldp_id_adjustlen(pkt, x)), ByteEnumField('subtype', 0x00, LLDP_PORT_ID_TLV_SUBTYPES), - StrLenField('id', '', length_from=lambda pkt: pkt._length - 1) + _LLDPidField('id', '', LLDP_PORT_ID_TLV_SUBTYPES, length_from=lambda pkt: pkt._length - 1) ] def _check(self): @@ -372,14 +418,6 @@ class LLDPDUPortID(LLDPDU): if conf.contribs['LLDP'].strict_mode() and not self.id: raise LLDPInvalidLengthField('id must be >= 1 characters long') - def post_dissect(self, s): - self._check() - return super(LLDPDUPortID, self).post_dissect(s) - - def do_build(self): - self._check() - return super(LLDPDUPortID, self).do_build() - class LLDPDUTimeToLive(LLDPDU): """ @@ -399,15 +437,6 @@ class LLDPDUTimeToLive(LLDPDU): raise LLDPInvalidLengthField('length must be 2 - got ' '{}'.format(self._length)) - def post_dissect(self, s): - self._check() - return super(LLDPDUTimeToLive, self).post_dissect(s) - - def do_build(self): - self._check() - return super(LLDPDUTimeToLive, self).do_build() - - class LLDPDUEndOfLLDPDU(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.1 / p. 26 @@ -428,14 +457,6 @@ class LLDPDUEndOfLLDPDU(LLDPDU): raise LLDPInvalidLengthField('length must be 0 - got ' '{}'.format(self._length)) - def post_dissect(self, s): - self._check() - return super(LLDPDUEndOfLLDPDU, self).post_dissect(s) - - def do_build(self): - self._check() - return super(LLDPDUEndOfLLDPDU, self).do_build() - class LLDPDUPortDescription(LLDPDU): """ @@ -519,14 +540,6 @@ class LLDPDUSystemCapabilities(LLDPDU): raise LLDPInvalidLengthField('length must be 4 - got ' '{}'.format(self._length)) - def post_dissect(self, s): - self._check() - return super(LLDPDUSystemCapabilities, self).post_dissect(s) - - def do_build(self): - self._check() - return super(LLDPDUSystemCapabilities, self).do_build() - class LLDPDUManagementAddress(LLDPDU): """ @@ -659,14 +672,6 @@ class LLDPDUManagementAddress(LLDPDU): 'management address must be 1..31 characters long - ' 'got string of size {}'.format(management_address_len)) - def post_dissect(self, s): - self._check() - return super(LLDPDUManagementAddress, self).post_dissect(s) - - def do_build(self): - self._check() - return super(LLDPDUManagementAddress, self).do_build() - class ThreeBytesEnumField(EnumField, ThreeBytesField): diff --git a/scapy/contrib/lldp.uts b/scapy/contrib/lldp.uts index 4c2b39db3..b8bfffef4 100644 --- a/scapy/contrib/lldp.uts +++ b/scapy/contrib/lldp.uts @@ -31,7 +31,7 @@ frm = Ether(frm) conf.contribs['LLDP'].strict_mode_disable() frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0') / \ - LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ LLDPDUTimeToLive() / \ LLDPDUEndOfLLDPDU() assert(len(raw(frm)) == 60) @@ -39,7 +39,7 @@ assert(len(raw(Ether(raw(frm))[Padding])) == 24) frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth012345678901234567890123') / \ - LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ LLDPDUTimeToLive() / \ LLDPDUEndOfLLDPDU() assert (len(raw(frm)) == 60) @@ -47,7 +47,7 @@ assert (len(raw(Ether(raw(frm))[Padding])) == 1) frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0123456789012345678901234') / \ - LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ LLDPDUTimeToLive() / \ LLDPDUEndOfLLDPDU() assert (len(raw(frm)) == 60) @@ -58,6 +58,13 @@ except IndexError: pass += Advanced test: check length of complex IDs + +pkt = Ether()/LLDPDUChassisID(id="ff:dd:ee:bb:aa:99", subtype=0x04)/LLDPDUPortID(subtype=0x03, id="aa:bb:cc:dd:ee:ff")/LLDPDUTimeToLive(ttl=120)/LLDPDUEndOfLLDPDU() +pkt = Ether(raw(pkt)) +assert pkt[LLDPDUChassisID]._length == 7 +assert pkt[LLDPDUPortID]._length == 7 + + strict mode handling - build = basic frame structure @@ -168,8 +175,8 @@ frm = Ether(frm.build()) conf.contribs['LLDP'].strict_mode_enable() 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')/\ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id='01:02:03:04:05:06')/\ LLDPDUTimeToLive()/\ LLDPDUSystemName(system_name='things will')/\ LLDPDUSystemCapabilities(telephone_available=1, @@ -188,8 +195,8 @@ frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/ \ frm = Ether(frm.build()) -assert str2mac(frm[LLDPDUChassisID].id) == '06:05:04:03:02:01' -assert str2mac(frm[LLDPDUPortID].id) == '01:02:03:04:05:06' +assert frm[LLDPDUChassisID].id == '06:05:04:03:02:01' +assert frm[LLDPDUPortID].id == '01:02:03:04:05:06' sys_capabilities = frm[LLDPDUSystemCapabilities] assert sys_capabilities.reserved_5_available == 0 assert sys_capabilities.reserved_4_available == 0 diff --git a/scapy/utils.py b/scapy/utils.py index d51c3bb44..91622e612 100644 --- a/scapy/utils.py +++ b/scapy/utils.py @@ -368,7 +368,7 @@ def fletcher16_checkbytes(binbuf, offset): def mac2str(mac): - return b"".join(chb(int(x, 16)) for x in mac.split(':')) + return b"".join(chb(int(x, 16)) for x in plain_str(mac).split(':')) def str2mac(s): if isinstance(s, str):