OSPF: scapy_ospf.py

File scapy_ospf.py, 13.8 KB (added by Dirk Loss, 4 years ago)
Line 
1"""
2OSPF extension for Scapy <http://www.secdev.org/scapy>
3
4This module provides Scapy layers for the Open Shortest Path First
5routing protocol as defined in RFC 2328.
6
7Copyright (c) 2008 Dirk Loss  :  mail dirk-loss de
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17GNU General Public License for more details.
18"""
19
20
21from scapy import *
22
23EXT_VERSION = "v0.8"
24
25
26class OSPFOptionsField(FlagsField):
27    def __init__(self, name="options", default=0, size=8,
28                 names=["8", "E", "MC", "NP", "L", "DC", "O", "DN"]):
29        FlagsField.__init__(self, name, default, size, names)
30
31_OSPF_types = { 1: "Hello",
32                2: "DBDesc",
33                3: "LSReq",
34                4: "LSUpd",
35                5: "LSAck" }
36
37class OSPF_Hdr(Packet):
38    name = "OSPF Header"
39    fields_desc = [
40                    ByteField("version", 2),
41                    ByteEnumField("type", 1, _OSPF_types),
42                    ShortField("len", None),
43                    IPField("src", "127.0.0.1"),
44                    IPField("area", "0.0.0.0"), # default: backbone
45                    XShortField("chksum", None),
46                    ShortEnumField("authtype", 0, {0:"Null", 1:"Simple", 2:"Crypto"}),
47                    # Null or Simple Authentication
48                    ConditionalField(XLongField("authdata", 0), lambda pkt:pkt.authtype != 2),
49                    # Crypto Authentication
50                    ConditionalField(XShortField("reserved", 0), lambda pkt:pkt.authtype == 2),
51                    ConditionalField(ByteField("keyid", 1), lambda pkt:pkt.authtype == 2),
52                    ConditionalField(ByteField("authdatalen", 0), lambda pkt:pkt.authtype == 2),
53                    ConditionalField(XIntField("seq", 0), lambda pkt:pkt.authtype == 2)
54                    # TODO: Support authdata (which is appended to the packets as if it were padding)
55                    ]
56                   
57    def post_build(self, p, pay):
58        p += pay
59        l = self.len
60        if l is None:
61            l = len(p)
62            p = p[:2]+struct.pack("!H",l)+p[4:]
63        if self.chksum is None:
64            if self.authtype == 2:
65                ck = 0   # Crypto, see RFC 2328, D.4.3
66            else:
67                # Checksum is calculated without authentication data
68                # Algorithm is the same as in IP()
69                ck = checksum(p[:16]+p[24:])
70                p = p[:12]+chr(ck>>8)+chr(ck&0xff)+p[14:]
71            # TODO: Handle Crypto: Add message digest  (RFC 2328, D.4.3)     
72        return p
73   
74    def hashret(self):
75        return struct.pack("H",self.area)+self.payload.hashret()
76   
77    def answers(self, other):
78        if (isinstance(other,OSPF_Hdr) and
79            self.area == other.area and
80            self.type == 5):  # Only acknowledgements answer other packets
81                return self.payload.answers(other.payload)
82        return 0
83
84
85class OSPF_Hello(Packet):
86    name = "OSPF Hello"
87    fields_desc = [ IPField("mask", "255.255.255.0"),
88                    ShortField("hellointerval", 10),
89                    OSPFOptionsField(),
90                    ByteField("prio", 1),
91                    IntField("deadinterval", 40),
92                    IPField("router", "0.0.0.0"),
93                    IPField("backup", "0.0.0.0"),
94                    ConditionalField(IPField("neighbor", "0.0.0.0"), lambda pkt: pkt.underlayer.len >= 48)
95                ]
96
97_OSPF_LStypes = { 1: "router",
98                  2: "network",
99                  3: "summaryIP",
100                  4: "summaryASBR",
101                  5: "external"}
102
103_OSPF_LSclasses = { 1: "OSPF_Router_LSA",
104                    2: "OSPF_Network_LSA",
105                    3: "OSPF_SummaryIP_LSA",
106                    4: "OSPF_SummaryASBR_LSA",
107                    5: "OSPF_External_LSA" }
108
109def ospf_lsa_checksum(lsa):
110    """ Fletcher checksum for OSPF LSAs, returned as a 2 byte string.
111   
112    Give the whole LSA packet as argument.
113    For details on the algorithm, see RFC 2328 chapter 12.1.7 and RFC 905 Annex B.
114    """
115    # This is based on the GPLed C implementation in Zebra <http://www.zebra.org/>
116
117    CHKSUM_OFFSET = 16   
118    if len(lsa) < CHKSUM_OFFSET:
119        raise Exception("LSA Packet too short (%s bytes)" % len(lsa))
120
121    c0 = c1 = 0
122    # Calculation is done with checksum set to zero
123    lsa = lsa[:CHKSUM_OFFSET] + "\x00\x00" + lsa[CHKSUM_OFFSET+2:] 
124    for char in lsa[2:]:  #  leave out age
125        c0 += ord(char)
126        c1 += c0
127    c0 %= 255
128    c1 %= 255
129
130    x = ((len(lsa) - CHKSUM_OFFSET - 1) * c0 - c1) % 255
131    if (x <= 0):
132      x += 255
133    y = 510 - c0 - x
134    if (y > 255):
135      y -= 255
136    #checksum = (x << 8) + y
137    return chr(x) + chr(y)
138
139   
140class OSPF_LSA_Hdr(Packet):
141    name = "OSPF LSA Header"
142    fields_desc = [ ShortField("age", 1),
143                    OSPFOptionsField(),
144                    ByteEnumField("type", 1, _OSPF_LStypes),
145                    IPField("id", "0.0.0.0"),
146                    IPField("adrouter", "0.0.0.0"),
147                    XIntField("seq", 1),
148                    XShortField("chksum", 0),
149                    ShortField("len", 36)]
150
151    def extract_padding(self, s):
152        return "", s
153
154   
155_OSPF_Router_LSA_types = { 1: "p2p",
156                           2: "transit",
157                           3: "stub",
158                           4: "virtual" }
159
160class OSPF_Link(Packet):
161    name = "OSPF Link"
162    fields_desc = [ IPField("id", "0.0.0.0"),
163                    IPField("data", "2.2.2.2"),
164                    ByteEnumField("type", 1, _OSPF_Router_LSA_types),
165                    ByteField("toscount", 0),
166                    ShortField("metric", 1),
167                    # TODO: define correct conditions
168                    ConditionalField(ByteField("tos", 0), lambda pkt: False),
169                    ConditionalField(ByteField("reserved", 0), lambda pkt: False),
170                    ConditionalField(ShortField("tosmetric", 0), lambda pkt: False)
171                    ]
172                   
173    def extract_padding(self,s):
174        return "", s
175
176
177def _LSAGuessPayloadClass(p, **kargs):
178    """ Guess the correct LSA class for a given payload """
179    # This is heavily based on scapy-cdp.py by Nicolas Bareil and Arnaud Ebalard
180    # XXX: This only works if all payload
181    cls = Raw
182    if len(p) >= 4:
183            typ = struct.unpack("!B", p[3])[0]
184            clsname = _OSPF_LSclasses.get(typ, "Raw")
185            cls = globals()[clsname]
186    return cls(p, **kargs)
187
188
189class OSPF_BaseLSA(Packet):
190    """ An abstract base class for Link State Advertisements """
191   
192    def post_build(self, p, pay):
193        length = self.len
194        if length is None:
195            length = len(p)
196            p = p[:18] + struct.pack("!H",length) + p[20:]
197        if self.chksum is None:
198            chksum = ospf_lsa_checksum(p)
199            p = p[:16] + chksum + p[18:]
200        return p    # p+pay?
201   
202    def extract_padding(self, s):
203        length = self.len
204        return "", s
205   
206
207class OSPF_Router_LSA(OSPF_BaseLSA):
208    name = "OSPF Router LSA"
209
210    fields_desc = [ ShortField("age", 1),
211                    OSPFOptionsField(),
212                    ByteField("type", 1),
213                    IPField("id", "0.0.0.0"),
214                    IPField("adrouter", "0.0.0.0"),
215                    XIntField("seq", 0x80000001),
216                    XShortField("chksum", None),
217                    ShortField("len", None),         
218                    FlagsField("flags", 0, 16, ["16", "15", "14", "13", "12", "V", "E", "B", "8", "7", "6", "5", "4", "3", "2", "1"]),
219                    FieldLenField("linkcount", None, count_of="linklist"),
220                    PacketListField("linklist", [], OSPF_Link,
221                                     count_from=lambda pkt:pkt.linkcount,
222                                     length_from=lambda pkt:pkt.linkcount*12)]
223
224
225class OSPF_Network_LSA(OSPF_BaseLSA):
226    name = "OSPF Network LSA"
227    fields_desc = [ ShortField("age", 1),
228                    OSPFOptionsField(),
229                    ByteField("type", 2),
230                    IPField("id", "0.0.0.0"),
231                    IPField("adrouter", "0.0.0.0"),
232                    XIntField("seq", 0x80000001),
233                    XShortField("chksum", None),
234                    ShortField("len", None),
235                    IPField("mask", "255.255.255.0"),               
236                    FieldListField("routerlist", [], IPField("", "0.0.0.1"),
237                                    length_from=lambda pkt: pkt.len - 24)]
238
239
240class OSPF_SummaryIP_LSA(OSPF_BaseLSA):
241    name = "OSPF Summary LSA (IP Network)"
242    fields_desc = [ ShortField("age", 1),
243                    OSPFOptionsField(),
244                    ByteField("type", 3),
245                    IPField("id", "0.0.0.0"),
246                    IPField("advertrouter", "0.0.0.0"),
247                    XIntField("seq", 0x80000001),
248                    XShortField("chksum", None),
249                    ShortField("len", None),
250                    IPField("mask", "255.255.255.0"),
251                    ByteField("reserved", 0),
252                    X3BytesField("metric", 0),
253                    # TODO: Define correct conditions
254                    ConditionalField(ByteField("tos", 0), lambda pkt:False),
255                    ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False)
256                    ]
257
258class OSPF_SummaryASBR_LSA(OSPF_BaseLSA):
259    name = "OSPF Summary LSA (AS Boundary Router)"
260    fields_desc = [ ShortField("age", 1),
261                    OSPFOptionsField(),
262                    ByteField("type", 4),
263                    IPField("id", "0.0.0.0"),
264                    IPField("adrouter", "0.0.0.0"),
265                    XIntField("seq", 0x80000001),
266                    XShortField("chksum", None),
267                    ShortField("len", None),
268                    IPField("mask", "255.255.255.0"),
269                    ByteField("reserved", 0),
270                    X3BytesField("metric", 0),
271                    # TODO: Define correct conditions
272                    ConditionalField(ByteField("tos", 0), lambda pkt:False),
273                    ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False)
274                    ]
275
276
277class OSPF_External_LSA(OSPF_BaseLSA):
278    name = "OSPF External LSA (ASBR)"
279    fields_desc = [
280                    ShortField("age", 1),
281                    OSPFOptionsField(),
282                    ByteField("type", 5),
283                    IPField("id", "0.0.0.0"),
284                    IPField("adrouter", "0.0.0.0"),
285                    XIntField("seq", 1),
286                    XShortField("chksum", None),
287                    ShortField("len", None),
288                    IPField("mask", "255.255.255.0"),
289                    FlagsField("ebit", 0, 1, ["E"]),
290                    BitField("reserved", 0, 7),
291                    X3BytesField("metric", 1), # XXX
292                    IPField("fwdaddr", "0.0.0.0"),
293                    XIntField("tag", 0),
294                    # TODO: Define correct conditions
295                    ConditionalField(ByteField("tos", 0), lambda pkt:False),
296                    ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False) 
297                    ]
298
299class OSPF_DBDesc(Packet):
300    name = "OSPF Database Description"
301    fields_desc = [ ShortField("mtu", 1500),
302                    OSPFOptionsField(),
303                    FlagsField("dbdescr", 0, 8, ["MS", "M", "I", "R", "4", "3", "2", "1"]),
304                    IntField("ddseq", 1),
305                    PacketListField("lsaheaders", None, OSPF_LSA_Hdr,
306                                    count_from = lambda pkt:None,
307                                    length_from = lambda pkt:pkt.underlayer.len - 24 - 8)]
308
309class OSPF_LSReq_Item(Packet):
310    name = "OSPF Link State Request (item)"   
311    fields_desc = [ IntEnumField("type", 1, _OSPF_LStypes),
312                    IPField("id", "0.0.0.0"),
313                    IPField("adrouter", "0.0.0.0")]
314                     
315    def extract_padding(self, s):
316        return "", s
317
318
319class OSPF_LSReq(Packet):
320    name = "OSPF Link State Request (container)"
321    fields_desc = [ PacketListField("requests", None, OSPF_LSReq_Item,
322                                  count_from = lambda pkt:None,
323                                  length_from = lambda pkt:pkt.underlayer.len - 24)]
324
325class OSPF_LSUpd(Packet):
326    name = "OSPF Link State Update"
327    fields_desc = [ FieldLenField("lsacount", None, fmt="!I", count_of="lsalist"),
328                    PacketListField("lsalist", [], _LSAGuessPayloadClass,
329                                count_from = lambda pkt:pkt.lsacount,
330                                length_from = lambda pkt:pkt.underlayer.len - 24)
331                                ]
332 
333
334
335class OSPF_LSAck(Packet):
336    name = "OSPF Link State Acknowledgement"
337    fields_desc = [ PacketListField("lsaheaders", None, OSPF_LSA_Hdr,
338                                   count_from = lambda pkt:None,
339                                   length_from = lambda pkt:pkt.underlayer.len - 24)]
340   
341    def answers(self, other):
342        if isinstance(other,OSPF_LSUpd):
343            for reqLSA in other.lsalist:
344                for ackLSA in self.lsaheaders:
345                    if (reqLSA.type == ackLSA.type and
346                        reqLSA.seq == ackLSA.seq):
347                        return 1
348        return 0
349   
350
351bind_layers(IP, OSPF_Hdr, proto=89)
352bind_layers(OSPF_Hdr, OSPF_Hello, type=1)
353bind_layers(OSPF_Hdr, OSPF_DBDesc, type=2)
354bind_layers(OSPF_Hdr, OSPF_LSReq, type=3)
355bind_layers(OSPF_Hdr, OSPF_LSUpd, type=4)
356bind_layers(OSPF_Hdr, OSPF_LSAck, type=5)
357
358
359if __name__ == "__main__":
360    interact(mydict=globals(), mybanner="OSPF extension %s" % EXT_VERSION)
361