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