Home | History | Annotate | Download | only in contrib
      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