Skip to content

Commit 84b9718

Browse files
authored
Merge pull request #1231 from gpotter2/lldp-length
LLDP: support complex ID types
2 parents bd60cd1 + 3935d91 commit 84b9718

3 files changed

Lines changed: 78 additions & 66 deletions

File tree

scapy/contrib/lldp.py

Lines changed: 63 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@
4242
4343
"""
4444
from scapy.config import conf
45-
from scapy.layers.dot11 import Packet
46-
from scapy.layers.l2 import Ether, Dot1Q, bind_layers, \
47-
BitField, StrLenField, ByteEnumField, BitEnumField, \
48-
BitFieldLenField, ShortField, Scapy_Exception, \
49-
XStrLenField, ByteField, EnumField, ThreeBytesField
45+
from scapy.error import log_runtime, Scapy_Exception
46+
from scapy.layers.l2 import Ether, Dot1Q
47+
from scapy.fields import MACField, IPField, BitField, \
48+
StrLenField, ByteEnumField, BitEnumField, \
49+
EnumField, ThreeBytesField, BitFieldLenField, \
50+
ShortField, XStrLenField
51+
from scapy.packet import Packet, Padding, bind_layers
5052
from scapy.modules.six.moves import range
5153
from scapy.data import ETHER_TYPES
5254
from scapy.compat import orb
@@ -282,6 +284,48 @@ def dissection_done(self, pkt):
282284

283285
super(LLDPDU, self).dissection_done(pkt)
284286

287+
def _check(self):
288+
"""Overwrited by LLDPU objects"""
289+
pass
290+
291+
def post_dissect(self, s):
292+
self._check()
293+
return super(LLDPDU, self).post_dissect(s)
294+
295+
def do_build(self):
296+
self._check()
297+
return super(LLDPDU, self).do_build()
298+
299+
class _LLDPidField(StrLenField):
300+
"""Class that selects the type of the ID field depending
301+
on the type of `subtype`"""
302+
__slots__ = StrLenField.__slots__ + ["subtypes_dict"]
303+
304+
def __init__(self, name, default, subtypes_dict, *args, **kargs):
305+
self.subtypes_dict = subtypes_dict
306+
super(_LLDPidField, self).__init__(name, default, *args, **kargs)
307+
308+
def m2i(self, pkt, x):
309+
cls = self.subtypes_dict.get(pkt.subtype, StrLenField)
310+
try:
311+
return (cls.m2i.__func__ if six.PY2 else cls.m2i)(self, pkt, x)
312+
except:
313+
log_runtime.exception("Failed to dissect " + self.name + " ! ")
314+
return StrLenField.m2i(self, pkt, x)
315+
316+
def i2m(self, pkt, x):
317+
cls = self.subtypes_dict.get(pkt.subtype, StrLenField)
318+
try:
319+
return (cls.i2m.__func__ if six.PY2 else cls.i2m)(self, pkt, x)
320+
except:
321+
log_runtime.exception("Failed to build " + self.name + " ! ")
322+
return StrLenField.i2m(self, pkt, x)
323+
324+
def _ldp_id_adjustlen(pkt, x):
325+
"""Return the length of the `id` field,
326+
according to its real encoded type"""
327+
f, v = pkt.getfield_and_val('id')
328+
return len(_LLDPidField.i2m(f, pkt, v)) + 1
285329

286330
class LLDPDUChassisID(LLDPDU):
287331
"""
@@ -299,6 +343,11 @@ class LLDPDUChassisID(LLDPDU):
299343
range(0x08, 0xff): 'reserved'
300344
}
301345

346+
LLDP_CHASSIS_ID_TLV_SUBTYPES_FIELDS = {
347+
0x04: MACField,
348+
0x05: IPField,
349+
}
350+
302351
SUBTYPE_RESERVED = 0x00
303352
SUBTYPE_CHASSIS_COMPONENT = 0x01
304353
SUBTYPE_INTERFACE_ALIAS = 0x02
@@ -311,9 +360,9 @@ class LLDPDUChassisID(LLDPDU):
311360
fields_desc = [
312361
BitEnumField('_type', 0x01, 7, LLDPDU.TYPES),
313362
BitFieldLenField('_length', None, 9, length_of='id',
314-
adjust=lambda pkt, x: len(pkt.id) + 1),
363+
adjust=lambda pkt, x: _ldp_id_adjustlen(pkt, x)),
315364
ByteEnumField('subtype', 0x00, LLDP_CHASSIS_ID_TLV_SUBTYPES),
316-
XStrLenField('id', '', length_from=lambda pkt: pkt._length - 1)
365+
_LLDPidField('id', '', LLDP_CHASSIS_ID_TLV_SUBTYPES_FIELDS, length_from=lambda pkt: pkt._length - 1)
317366
]
318367

319368
def _check(self):
@@ -323,14 +372,6 @@ def _check(self):
323372
if conf.contribs['LLDP'].strict_mode() and not self.id:
324373
raise LLDPInvalidLengthField('id must be >= 1 characters long')
325374

326-
def post_dissect(self, s):
327-
self._check()
328-
return super(LLDPDUChassisID, self).post_dissect(s)
329-
330-
def do_build(self):
331-
self._check()
332-
return super(LLDPDUChassisID, self).do_build()
333-
334375

335376
class LLDPDUPortID(LLDPDU):
336377
"""
@@ -348,6 +389,11 @@ class LLDPDUPortID(LLDPDU):
348389
range(0x08, 0xff): 'reserved'
349390
}
350391

392+
LLDP_PORT_ID_TLV_SUBTYPES = {
393+
0x03: MACField,
394+
0x04: IPField,
395+
}
396+
351397
SUBTYPE_RESERVED = 0x00
352398
SUBTYPE_INTERFACE_ALIAS = 0x01
353399
SUBTYPE_PORT_COMPONENT = 0x02
@@ -360,9 +406,9 @@ class LLDPDUPortID(LLDPDU):
360406
fields_desc = [
361407
BitEnumField('_type', 0x02, 7, LLDPDU.TYPES),
362408
BitFieldLenField('_length', None, 9, length_of='id',
363-
adjust=lambda pkt, x: len(pkt.id) + 1),
409+
adjust=lambda pkt, x: _ldp_id_adjustlen(pkt, x)),
364410
ByteEnumField('subtype', 0x00, LLDP_PORT_ID_TLV_SUBTYPES),
365-
StrLenField('id', '', length_from=lambda pkt: pkt._length - 1)
411+
_LLDPidField('id', '', LLDP_PORT_ID_TLV_SUBTYPES, length_from=lambda pkt: pkt._length - 1)
366412
]
367413

368414
def _check(self):
@@ -372,14 +418,6 @@ def _check(self):
372418
if conf.contribs['LLDP'].strict_mode() and not self.id:
373419
raise LLDPInvalidLengthField('id must be >= 1 characters long')
374420

375-
def post_dissect(self, s):
376-
self._check()
377-
return super(LLDPDUPortID, self).post_dissect(s)
378-
379-
def do_build(self):
380-
self._check()
381-
return super(LLDPDUPortID, self).do_build()
382-
383421

384422
class LLDPDUTimeToLive(LLDPDU):
385423
"""
@@ -399,15 +437,6 @@ def _check(self):
399437
raise LLDPInvalidLengthField('length must be 2 - got '
400438
'{}'.format(self._length))
401439

402-
def post_dissect(self, s):
403-
self._check()
404-
return super(LLDPDUTimeToLive, self).post_dissect(s)
405-
406-
def do_build(self):
407-
self._check()
408-
return super(LLDPDUTimeToLive, self).do_build()
409-
410-
411440
class LLDPDUEndOfLLDPDU(LLDPDU):
412441
"""
413442
ieee 802.1ab-2016 - sec. 8.5.1 / p. 26
@@ -428,14 +457,6 @@ def _check(self):
428457
raise LLDPInvalidLengthField('length must be 0 - got '
429458
'{}'.format(self._length))
430459

431-
def post_dissect(self, s):
432-
self._check()
433-
return super(LLDPDUEndOfLLDPDU, self).post_dissect(s)
434-
435-
def do_build(self):
436-
self._check()
437-
return super(LLDPDUEndOfLLDPDU, self).do_build()
438-
439460

440461
class LLDPDUPortDescription(LLDPDU):
441462
"""
@@ -519,14 +540,6 @@ def _check(self):
519540
raise LLDPInvalidLengthField('length must be 4 - got '
520541
'{}'.format(self._length))
521542

522-
def post_dissect(self, s):
523-
self._check()
524-
return super(LLDPDUSystemCapabilities, self).post_dissect(s)
525-
526-
def do_build(self):
527-
self._check()
528-
return super(LLDPDUSystemCapabilities, self).do_build()
529-
530543

531544
class LLDPDUManagementAddress(LLDPDU):
532545
"""
@@ -659,14 +672,6 @@ def _check(self):
659672
'management address must be 1..31 characters long - '
660673
'got string of size {}'.format(management_address_len))
661674

662-
def post_dissect(self, s):
663-
self._check()
664-
return super(LLDPDUManagementAddress, self).post_dissect(s)
665-
666-
def do_build(self):
667-
self._check()
668-
return super(LLDPDUManagementAddress, self).do_build()
669-
670675

671676
class ThreeBytesEnumField(EnumField, ThreeBytesField):
672677

scapy/contrib/lldp.uts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,23 @@ frm = Ether(frm)
3131
conf.contribs['LLDP'].strict_mode_disable()
3232
frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \
3333
LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0') / \
34-
LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \
34+
LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \
3535
LLDPDUTimeToLive() / \
3636
LLDPDUEndOfLLDPDU()
3737
assert(len(raw(frm)) == 60)
3838
assert(len(raw(Ether(raw(frm))[Padding])) == 24)
3939

4040
frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \
4141
LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth012345678901234567890123') / \
42-
LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \
42+
LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \
4343
LLDPDUTimeToLive() / \
4444
LLDPDUEndOfLLDPDU()
4545
assert (len(raw(frm)) == 60)
4646
assert (len(raw(Ether(raw(frm))[Padding])) == 1)
4747

4848
frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \
4949
LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0123456789012345678901234') / \
50-
LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \
50+
LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \
5151
LLDPDUTimeToLive() / \
5252
LLDPDUEndOfLLDPDU()
5353
assert (len(raw(frm)) == 60)
@@ -58,6 +58,13 @@ except IndexError:
5858
pass
5959

6060

61+
= Advanced test: check length of complex IDs
62+
63+
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()
64+
pkt = Ether(raw(pkt))
65+
assert pkt[LLDPDUChassisID]._length == 7
66+
assert pkt[LLDPDUPortID]._length == 7
67+
6168
+ strict mode handling - build
6269
= basic frame structure
6370

@@ -168,8 +175,8 @@ frm = Ether(frm.build())
168175
conf.contribs['LLDP'].strict_mode_enable()
169176

170177
frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/ \
171-
LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \
172-
LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\
178+
LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \
179+
LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id='01:02:03:04:05:06')/\
173180
LLDPDUTimeToLive()/\
174181
LLDPDUSystemName(system_name='things will')/\
175182
LLDPDUSystemCapabilities(telephone_available=1,
@@ -188,8 +195,8 @@ frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/ \
188195

189196
frm = Ether(frm.build())
190197

191-
assert str2mac(frm[LLDPDUChassisID].id) == '06:05:04:03:02:01'
192-
assert str2mac(frm[LLDPDUPortID].id) == '01:02:03:04:05:06'
198+
assert frm[LLDPDUChassisID].id == '06:05:04:03:02:01'
199+
assert frm[LLDPDUPortID].id == '01:02:03:04:05:06'
193200
sys_capabilities = frm[LLDPDUSystemCapabilities]
194201
assert sys_capabilities.reserved_5_available == 0
195202
assert sys_capabilities.reserved_4_available == 0

scapy/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ def fletcher16_checkbytes(binbuf, offset):
368368

369369

370370
def mac2str(mac):
371-
return b"".join(chb(int(x, 16)) for x in mac.split(':'))
371+
return b"".join(chb(int(x, 16)) for x in plain_str(mac).split(':'))
372372

373373
def str2mac(s):
374374
if isinstance(s, str):

0 commit comments

Comments
 (0)