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