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