1#! /usr/bin/env python
2
3# scapy.contrib.description = Cisco Discovery Protocol
4# scapy.contrib.status = loads
5
6#############################################################################
7##                                                                         ##
8## cdp.py --- Cisco Discovery Protocol (CDP) extension for Scapy           ##
9##                                                                         ##
10## Copyright (C) 2006    Nicolas Bareil  <nicolas.bareil AT eads DOT net>  ##
11##                       Arnaud Ebalard  <arnaud.ebalard AT eads DOT net>  ##
12##                       EADS/CRC security team                            ##
13##                                                                         ##
14## This file is part of Scapy                                              ##
15## Scapy is free software: you can redistribute it and/or modify it        ##
16## under the terms of the GNU General Public License version 2 as          ##
17## published by the Free Software Foundation; version 2.                   ##
18##                                                                         ##
19## This program is distributed in the hope that it will be useful, but     ##
20## WITHOUT ANY WARRANTY; without even the implied warranty of              ##
21## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       ##
22## General Public License for more details.                                ##
23##                                                                         ##
24#############################################################################
25
26from __future__ import absolute_import
27from scapy.packet import *
28from scapy.fields import *
29from scapy.layers.inet6 import *
30from scapy.compat import orb
31from scapy.modules.six.moves import range
32
33
34#####################################################################
35# Helpers and constants
36#####################################################################
37
38# CDP TLV classes keyed by type
39_cdp_tlv_cls = { 0x0001: "CDPMsgDeviceID",
40                 0x0002: "CDPMsgAddr",
41                 0x0003: "CDPMsgPortID",
42                 0x0004: "CDPMsgCapabilities",
43                 0x0005: "CDPMsgSoftwareVersion",
44                 0x0006: "CDPMsgPlatform",
45                 0x0007: "CDPMsgIPPrefix",
46                 0x0008: "CDPMsgProtoHello",
47                 0x0009: "CDPMsgVTPMgmtDomain", # CDPv2
48                 0x000a: "CDPMsgNativeVLAN",    # CDPv2
49                 0x000b: "CDPMsgDuplex",        #
50#                 0x000c: "CDPMsgGeneric",
51#                 0x000d: "CDPMsgGeneric",
52                 0x000e: "CDPMsgVoIPVLANReply",
53                 0x000f: "CDPMsgVoIPVLANQuery",
54                 0x0010: "CDPMsgPower",
55                 0x0011: "CDPMsgMTU",
56                 0x0012: "CDPMsgTrustBitmap",
57                 0x0013: "CDPMsgUntrustedPortCoS",
58#                 0x0014: "CDPMsgSystemName",
59#                 0x0015: "CDPMsgSystemOID",
60                 0x0016: "CDPMsgMgmtAddr",
61#                 0x0017: "CDPMsgLocation",
62                 0x0019: "CDPMsgUnknown19",
63#                 0x001a: "CDPPowerAvailable"
64                 }
65
66_cdp_tlv_types = { 0x0001: "Device ID",
67                   0x0002: "Addresses",
68                   0x0003: "Port ID",
69                   0x0004: "Capabilities",
70                   0x0005: "Software Version",
71                   0x0006: "Platform",
72                   0x0007: "IP Prefix",
73                   0x0008: "Protocol Hello",
74                   0x0009: "VTP Management Domain", # CDPv2
75                   0x000a: "Native VLAN",    # CDPv2
76                   0x000b: "Duplex",        #
77                   0x000c: "CDP Unknown command (send us a pcap file)",
78                   0x000d: "CDP Unknown command (send us a pcap file)",
79                   0x000e: "VoIP VLAN Reply",
80                   0x000f: "VoIP VLAN Query",
81                   0x0010: "Power",
82                   0x0011: "MTU",
83                   0x0012: "Trust Bitmap",
84                   0x0013: "Untrusted Port CoS",
85                   0x0014: "System Name",
86                   0x0015: "System OID",
87                   0x0016: "Management Address",
88                   0x0017: "Location",
89                   0x0018: "CDP Unknown command (send us a pcap file)",
90                   0x0019: "CDP Unknown command (send us a pcap file)",
91                   0x001a: "Power Available"}
92
93def _CDPGuessPayloadClass(p, **kargs):
94    cls = conf.raw_layer
95    if len(p) >= 2:
96        t = struct.unpack("!H", p[:2])[0]
97        clsname = _cdp_tlv_cls.get(t, "CDPMsgGeneric")
98        cls = globals()[clsname]
99
100    return cls(p, **kargs)
101
102class CDPMsgGeneric(Packet):
103    name = "CDP Generic Message"
104    fields_desc = [ XShortEnumField("type", None, _cdp_tlv_types),
105                    FieldLenField("len", None, "val", "!H"),
106                    StrLenField("val", "", length_from=lambda x:x.len - 4) ]
107
108
109    def guess_payload_class(self, p):
110        return conf.padding_layer # _CDPGuessPayloadClass
111
112class CDPMsgDeviceID(CDPMsgGeneric):
113    name = "Device ID"
114    type = 0x0001
115
116_cdp_addr_record_ptype = {0x01: "NLPID", 0x02: "802.2"}
117_cdp_addrrecord_proto_ip = b"\xcc"
118_cdp_addrrecord_proto_ipv6 = b"\xaa\xaa\x03\x00\x00\x00\x86\xdd"
119
120class CDPAddrRecord(Packet):
121    name = "CDP Address"
122    fields_desc = [ ByteEnumField("ptype", 0x01, _cdp_addr_record_ptype),
123                    FieldLenField("plen", None, "proto", "B"),
124                    StrLenField("proto", None, length_from=lambda x:x.plen),
125                    FieldLenField("addrlen", None, length_of=lambda x:x.addr),
126                    StrLenField("addr", None, length_from=lambda x:x.addrlen)]
127
128    def guess_payload_class(self, p):
129        return conf.padding_layer
130
131class CDPAddrRecordIPv4(CDPAddrRecord):
132    name = "CDP Address IPv4"
133    fields_desc = [ ByteEnumField("ptype", 0x01, _cdp_addr_record_ptype),
134                    FieldLenField("plen", 1, "proto", "B"),
135                    StrLenField("proto", _cdp_addrrecord_proto_ip, length_from=lambda x:x.plen),
136                    ShortField("addrlen", 4),
137                    IPField("addr", "0.0.0.0")]
138
139class CDPAddrRecordIPv6(CDPAddrRecord):
140    name = "CDP Address IPv6"
141    fields_desc = [ ByteEnumField("ptype", 0x02, _cdp_addr_record_ptype),
142                    FieldLenField("plen", 8, "proto", "B"),
143                    StrLenField("proto", _cdp_addrrecord_proto_ipv6, length_from=lambda x:x.plen),
144                    ShortField("addrlen", 16),
145                    IP6Field("addr", "::1")]
146
147def _CDPGuessAddrRecord(p, **kargs):
148    cls = conf.raw_layer
149    if len(p) >= 2:
150        plen = orb(p[1])
151        proto = p[2:plen + 2]
152
153        if proto == _cdp_addrrecord_proto_ip:
154            clsname = "CDPAddrRecordIPv4"
155        elif proto == _cdp_addrrecord_proto_ipv6:
156            clsname = "CDPAddrRecordIPv6"
157        else:
158            clsname = "CDPAddrRecord"
159
160        cls = globals()[clsname]
161
162    return cls(p, **kargs)
163
164class CDPMsgAddr(CDPMsgGeneric):
165    name = "Addresses"
166    fields_desc = [ XShortEnumField("type", 0x0002, _cdp_tlv_types),
167                    ShortField("len", None),
168                    FieldLenField("naddr", None, "addr", "!I"),
169                    PacketListField("addr", [], _CDPGuessAddrRecord, count_from=lambda x:x.naddr) ]
170
171    def post_build(self, pkt, pay):
172        if self.len is None:
173            l = 8 + len(self.addr) * 9
174            pkt = pkt[:2] + struct.pack("!H", l) + pkt[4:]
175        p = pkt + pay
176        return p
177
178class CDPMsgPortID(CDPMsgGeneric):
179    name = "Port ID"
180    fields_desc = [ XShortEnumField("type", 0x0003, _cdp_tlv_types),
181                    FieldLenField("len", None, "iface", "!H"),
182                    StrLenField("iface", "Port 1", length_from=lambda x:x.len - 4) ]
183
184
185_cdp_capabilities = ["Router",
186                     "TransparentBridge",
187                     "SourceRouteBridge",
188                     "Switch",
189                     "Host",
190                     "IGMPCapable",
191                     "Repeater"] + ["Bit%d" % x for x in range(25, 0, -1)]
192
193
194class CDPMsgCapabilities(CDPMsgGeneric):
195    name = "Capabilities"
196    fields_desc = [ XShortEnumField("type", 0x0004, _cdp_tlv_types),
197                    ShortField("len", 8),
198                    FlagsField("cap", 0, 32,  _cdp_capabilities) ]
199
200
201class CDPMsgSoftwareVersion(CDPMsgGeneric):
202    name = "Software Version"
203    type = 0x0005
204
205
206class CDPMsgPlatform(CDPMsgGeneric):
207    name = "Platform"
208    type = 0x0006
209
210_cdp_duplex = { 0x00: "Half", 0x01: "Full" }
211
212# ODR Routing
213class CDPMsgIPPrefix(CDPMsgGeneric):
214    name = "IP Prefix"
215    type = 0x0007
216    fields_desc = [ XShortEnumField("type", 0x0007, _cdp_tlv_types),
217                    ShortField("len", 8),
218                    IPField("defaultgw", "192.168.0.1") ]
219
220class CDPMsgProtoHello(CDPMsgGeneric):
221    name = "Protocol Hello"
222    type = 0x0008
223    fields_desc = [ XShortEnumField("type", 0x0008, _cdp_tlv_types),
224                    ShortField("len", 32),
225                    X3BytesField("oui", 0x00000c),
226                    XShortField("protocol_id", 0x0),
227                    # TLV length (len) - 2 (type) - 2 (len) - 3 (OUI) - 2
228                    # (Protocol ID)
229                    StrLenField("data", "", length_from=lambda p: p.len - 9) ]
230
231class CDPMsgVTPMgmtDomain(CDPMsgGeneric):
232    name = "VTP Management Domain"
233    type = 0x0009
234
235class CDPMsgNativeVLAN(CDPMsgGeneric):
236    name = "Native VLAN"
237    fields_desc = [ XShortEnumField("type", 0x000a, _cdp_tlv_types),
238                    ShortField("len", 6),
239                    ShortField("vlan", 1) ]
240
241class CDPMsgDuplex(CDPMsgGeneric):
242    name = "Duplex"
243    fields_desc = [ XShortEnumField("type", 0x000b, _cdp_tlv_types),
244                    ShortField("len", 5),
245                    ByteEnumField("duplex", 0x00, _cdp_duplex) ]
246
247class CDPMsgVoIPVLANReply(CDPMsgGeneric):
248    name = "VoIP VLAN Reply"
249    fields_desc = [ XShortEnumField("type", 0x000e, _cdp_tlv_types),
250                    ShortField("len", 7),
251                    ByteField("status?", 1),
252                    ShortField("vlan", 1) ]
253
254
255class CDPMsgVoIPVLANQuery(CDPMsgGeneric):
256    name = "VoIP VLAN Query"
257    type = 0x000f
258    fields_desc = [ XShortEnumField("type", 0x000f, _cdp_tlv_types),
259                    ShortField("len", 7),
260                    XByteField("unknown1", 0),
261                    ShortField("vlan", 1),
262                    # TLV length (len) - 2 (type) - 2 (len) - 1 (unknown1) - 2 (vlan)
263                    StrLenField("unknown2", "", length_from=lambda p: p.len - 7) ]
264
265
266class _CDPPowerField(ShortField):
267    def i2repr(self, pkt, x):
268        if x is None:
269            x = 0
270        return "%d mW" % x
271
272
273class CDPMsgPower(CDPMsgGeneric):
274    name = "Power"
275    # Check if field length is fixed (2 bytes)
276    fields_desc = [ XShortEnumField("type", 0x0010, _cdp_tlv_types),
277                    ShortField("len", 6),
278                    _CDPPowerField("power", 1337)]
279
280
281class CDPMsgMTU(CDPMsgGeneric):
282    name = "MTU"
283    # Check if field length is fixed (2 bytes)
284    fields_desc = [ XShortEnumField("type", 0x0011, _cdp_tlv_types),
285                    ShortField("len", 6),
286                    ShortField("mtu", 1500)]
287
288class CDPMsgTrustBitmap(CDPMsgGeneric):
289    name = "Trust Bitmap"
290    fields_desc = [ XShortEnumField("type", 0x0012, _cdp_tlv_types),
291                    ShortField("len", 5),
292                    XByteField("trust_bitmap", 0x0) ]
293
294class CDPMsgUntrustedPortCoS(CDPMsgGeneric):
295    name = "Untrusted Port CoS"
296    fields_desc = [ XShortEnumField("type", 0x0013, _cdp_tlv_types),
297                    ShortField("len", 5),
298                    XByteField("untrusted_port_cos", 0x0) ]
299
300class CDPMsgMgmtAddr(CDPMsgAddr):
301    name = "Management Address"
302    type = 0x0016
303
304class CDPMsgUnknown19(CDPMsgGeneric):
305    name = "Unknown CDP Message"
306    type = 0x0019
307
308class CDPMsg(CDPMsgGeneric):
309    name = "CDP "
310    fields_desc = [ XShortEnumField("type", None, _cdp_tlv_types),
311                    FieldLenField("len", None, "val", "!H"),
312                    StrLenField("val", "", length_from=lambda x:x.len - 4) ]
313
314class _CDPChecksum:
315    def _check_len(self, pkt):
316        """Check for odd packet length and pad according to Cisco spec.
317        This padding is only used for checksum computation.  The original
318        packet should not be altered."""
319        if len(pkt) % 2:
320            last_chr = pkt[-1]
321            if last_chr <= b'\x80':
322                return pkt[:-1] + b'\x00' + last_chr
323            else:
324                return pkt[:-1] + b'\xff' + chb(orb(last_chr) - 1)
325        else:
326            return pkt
327
328    def post_build(self, pkt, pay):
329        p = pkt + pay
330        if self.cksum is None:
331            cksum = checksum(self._check_len(p))
332            p = p[:2] + struct.pack("!H", cksum) + p[4:]
333        return p
334
335class CDPv2_HDR(_CDPChecksum, CDPMsgGeneric):
336    name = "Cisco Discovery Protocol version 2"
337    fields_desc = [ ByteField("vers", 2),
338                    ByteField("ttl", 180),
339                    XShortField("cksum", None),
340                    PacketListField("msg", [], _CDPGuessPayloadClass) ]
341
342bind_layers(SNAP, CDPv2_HDR, {"code": 0x2000, "OUI": 0xC})
343
344