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 26 from __future__ import absolute_import 27 from scapy.packet import * 28 from scapy.fields import * 29 from scapy.layers.inet6 import * 30 from scapy.compat import orb 31 from 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 93 def _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 102 class 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 112 class 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 120 class 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 131 class 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 139 class 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 147 def _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 164 class 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 178 class 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 194 class 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 201 class CDPMsgSoftwareVersion(CDPMsgGeneric): 202 name = "Software Version" 203 type = 0x0005 204 205 206 class CDPMsgPlatform(CDPMsgGeneric): 207 name = "Platform" 208 type = 0x0006 209 210 _cdp_duplex = { 0x00: "Half", 0x01: "Full" } 211 212 # ODR Routing 213 class 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 220 class 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 231 class CDPMsgVTPMgmtDomain(CDPMsgGeneric): 232 name = "VTP Management Domain" 233 type = 0x0009 234 235 class CDPMsgNativeVLAN(CDPMsgGeneric): 236 name = "Native VLAN" 237 fields_desc = [ XShortEnumField("type", 0x000a, _cdp_tlv_types), 238 ShortField("len", 6), 239 ShortField("vlan", 1) ] 240 241 class 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 247 class 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 255 class 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 266 class _CDPPowerField(ShortField): 267 def i2repr(self, pkt, x): 268 if x is None: 269 x = 0 270 return "%d mW" % x 271 272 273 class 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 281 class 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 288 class 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 294 class 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 300 class CDPMsgMgmtAddr(CDPMsgAddr): 301 name = "Management Address" 302 type = 0x0016 303 304 class CDPMsgUnknown19(CDPMsgGeneric): 305 name = "Unknown CDP Message" 306 type = 0x0019 307 308 class 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 314 class _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 335 class 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 342 bind_layers(SNAP, CDPv2_HDR, {"code": 0x2000, "OUI": 0xC}) 343 344