OSPF: scapy_ospf.py

File scapy_ospf.py, 13.8 kB (added by Dirk Loss, 9 months ago)
Line 
1 """
2 OSPF extension for Scapy <http://www.secdev.org/scapy>
3
4 This module provides Scapy layers for the Open Shortest Path First
5 routing protocol as defined in RFC 2328.
6
7 Copyright (c) 2008 Dirk Loss  :  mail dirk-loss de
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 """
19
20
21 from scapy import *
22
23 EXT_VERSION = "v0.8"
24
25
26 class 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
37 class 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
85 class 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
109 def 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    
140 class 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
160 class 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
177 def _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
189 class 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
207 class 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
225 class 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
240 class 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
258 class 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
277 class 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
299 class 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
309 class 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
319 class 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
325 class 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
335 class 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
351 bind_layers(IP, OSPF_Hdr, proto=89)
352 bind_layers(OSPF_Hdr, OSPF_Hello, type=1)
353 bind_layers(OSPF_Hdr, OSPF_DBDesc, type=2)
354 bind_layers(OSPF_Hdr, OSPF_LSReq, type=3)
355 bind_layers(OSPF_Hdr, OSPF_LSUpd, type=4)
356 bind_layers(OSPF_Hdr, OSPF_LSAck, type=5)
357
358
359 if __name__ == "__main__":
360     interact(mydict=globals(), mybanner="OSPF extension %s" % EXT_VERSION)
361