File: cdp.py

package info (click to toggle)
python-dpkt 1.9.8-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,104 kB
  • sloc: python: 14,911; makefile: 23
file content (202 lines) | stat: -rw-r--r-- 6,450 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# $Id: cdp.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""Cisco Discovery Protocol."""
from __future__ import absolute_import

from . import dpkt

CDP_DEVID = 1  # string
CDP_ADDRESS = 2
CDP_PORTID = 3  # string
CDP_CAPABILITIES = 4  # 32-bit bitmask
CDP_VERSION = 5  # string
CDP_PLATFORM = 6  # string
CDP_IPPREFIX = 7

CDP_VTP_MGMT_DOMAIN = 9  # string
CDP_NATIVE_VLAN = 10  # 16-bit integer
CDP_DUPLEX = 11  # 8-bit boolean
CDP_TRUST_BITMAP = 18  # 8-bit bitmask0x13
CDP_UNTRUST_COS = 19  # 8-bit port
CDP_SYSTEM_NAME = 20  # string
CDP_SYSTEM_OID = 21  # 10-byte binary string
CDP_MGMT_ADDRESS = 22  # 32-bit number of addrs, Addresses
CDP_LOCATION = 23  # string


class CDP(dpkt.Packet):
    """Cisco Discovery Protocol.

    Cisco Discovery Protocol (CDP) is a proprietary Data Link Layer protocol developed by Cisco Systems in 1994
    by Keith McCloghrie and Dino Farinacci. It is used to share information about other directly connected
    Cisco equipment, such as the operating system version and IP address.

    See more on
    https://en.wikipedia.org/wiki/Cisco_Discovery_Protocol

    Attributes:
        __hdr__: Header fields of CDP.
            version: (int): CDP protocol version. (1 byte)
            ttl: (int): Time to live. The amount of time in seconds that a receiver should retain the information
                contained in this packet. (1 byte)
            sum: (int): Checksum. (2 bytes)
    """

    __hdr__ = (
        ('version', 'B', 2),
        ('ttl', 'B', 180),
        ('sum', 'H', 0)
    )

    class TLV(dpkt.Packet):
        """Type–length–value

        When constructing the packet, len is not mandatory:
        if not provided, then self.data must be this exact TLV payload

        Attributes:
            __hdr__: Header fields of TLV.
                type: (int): Type (2 bytes)
                len: (int): The total length in bytes of the Type, Length and Data fields. (2 bytes)
        """

        __hdr__ = (
            ('type', 'H', 0),
            ('len', 'H', 0)
        )

        def data_len(self):
            if self.len:
                return self.len - self.__hdr_len__
            return len(self.data)

        def unpack(self, buf):
            dpkt.Packet.unpack(self, buf)
            self.data = self.data[:self.data_len()]

        def __len__(self):
            return self.__hdr_len__ + len(self.data)

        def __bytes__(self):
            if hasattr(self, 'len') and not self.len:
                self.len = len(self)
            return self.pack_hdr() + bytes(self.data)

    class Address(TLV):
        # XXX - only handle NLPID/IP for now
        __hdr__ = (
            ('ptype', 'B', 1),  # protocol type (NLPID)
            ('plen', 'B', 1),  # protocol length
            ('p', 'B', 0xcc),  # IP
            ('alen', 'H', 4)  # address length
        )

        def data_len(self):
            return self.alen

    class TLV_Addresses(TLV):
        __hdr__ = (
            ('type', 'H', CDP_ADDRESS),
            ('len', 'H', 0),    # 17),
            ('Addresses', 'L', 1),
        )

    def unpack(self, buf):
        dpkt.Packet.unpack(self, buf)
        buf = self.data
        l_ = []
        while buf:
            # find the right TLV according to Type value
            tlv_find_type = self.TLV(buf).type
            # if this TLV is not in tlv_types, use the default TLV class
            tlv = self.tlv_types.get(tlv_find_type, self.TLV)(buf)
            l_.append(bytes(tlv))
            buf = buf[len(tlv):]
        self.tlvs = l_
        self.data = b''.join(l_)

    def __len__(self):
        return self.__hdr_len__ + len(self.data)

    def __bytes__(self):
        data = bytes(self.data)
        if not self.sum:
            self.sum = dpkt.in_cksum(self.pack_hdr() + data)
        return self.pack_hdr() + data

    # keep here the TLV classes whose header is different from the generic TLV header (example : TLV_Addresses)
    tlv_types = {CDP_ADDRESS: TLV_Addresses}


def test_cdp():
    import socket
    from . import ethernet

    ss = (b'\x02\xb4\xdf\x93\x00\x01\x00\x09\x63\x69\x73\x63\x6f\x00\x02\x00\x11\x00\x00\x00\x01'
          b'\x01\x01\xcc\x00\x04\xc0\xa8\x01\x67')
    rr1 = CDP(ss)
    assert bytes(rr1) == ss

    # construction
    ss = (b'\x02\xb4\xdf\x93\x00\x01\x00\x09\x63\x69\x73\x63\x6f\x00\x02\x00\x11\x00\x00\x00\x01'
          b'\x01\x01\xcc\x00\x04\xc0\xa8\x01\x67')
    p1 = CDP.TLV_Addresses(data=CDP.Address(data=socket.inet_aton('192.168.1.103')))
    p2 = CDP.TLV(type=CDP_DEVID, data=b'cisco')
    data = p2.pack() + p1.pack()
    rr2 = CDP(data=data)
    assert bytes(rr2) == ss

    s = (b'\x01\x00\x0c\xcc\xcc\xcc\xc4\x022k\x00\x00\x01T\xaa\xaa\x03\x00\x00\x0c \x00\x02\xb4,B'
         b'\x00\x01\x00\x06R2\x00\x05\x00\xffCisco IOS Software, 3700 Software (C3745-ADVENTERPRI'
         b'SEK9_SNA-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)\nTechnical Support: http://www.'
         b'cisco.com/techsupport\nCopyright (c) 1986-2010 by Cisco Systems, Inc.\nCompiled Wed 18'
         b'-Aug-10 08:18 by prod_rel_team\x00\x06\x00\x0eCisco 3745\x00\x02\x00\x11\x00\x00\x00\x01'
         b'\x01\x01\xcc\x00\x04\n\x00\x00\x02\x00\x03\x00\x13FastEthernet0/0\x00\x04\x00\x08\x00'
         b'\x00\x00)\x00\t\x00\x04\x00\x0b\x00\x05\x00')
    eth = ethernet.Ethernet(s)
    assert isinstance(eth.data.data, CDP)
    assert len(eth.data.data.tlvs) == 8  # number of CDP TLVs; ensures they are decoded
    assert str(eth) == str(s)
    assert len(eth) == len(s)


def test_tlv():
    from binascii import unhexlify
    # len field set to 0
    buf_no_len = unhexlify(
        '0000'  # type
        '0000'  # len
        'abcd'  # data
    )

    buf_with_len = unhexlify(
        '0000'  # type
        '0006'  # len
        'abcd'  # data
    )
    tlv = CDP.TLV(buf_no_len)
    assert tlv.type == 0
    assert tlv.len == 0
    assert tlv.data_len() == 2
    assert tlv.data == b'\xab\xcd'
    assert bytes(tlv) == buf_with_len

    # len field set manually
    tlv = CDP.TLV(buf_with_len)
    assert tlv.type == 0
    assert tlv.len == 6
    assert tlv.data_len() == 2
    assert tlv.data == b'\xab\xcd'
    assert bytes(tlv) == buf_with_len


def test_address():
    from binascii import unhexlify
    buf = unhexlify(
        '00'    # ptype
        '11'    # plen
        '22'    # p
        '3333'  # alen
    )
    address = CDP.Address(buf)
    assert address.data_len() == 0x3333