root/scapy/plist.py

Revision 950:800294eaa8c0, 16.0 kB (checked in by Phil <phil@secdev.org>, 2 weeks ago)

Fixed NoTheme? and factorized some code (ticket #143)

Line 
1 ## This file is part of Scapy
2 ## See http://www.secdev.org/projects/scapy for more informations
3 ## Copyright (C) Philippe Biondi <phil@secdev.org>
4 ## This program is published under a GPLv2 license
5
6 import os
7 from config import conf
8 from base_classes import BasePacket,BasePacketList
9 from packet import Padding
10
11 from utils import do_graph,hexdump,make_table,make_lined_table,make_tex_table
12
13 import arch
14 if arch.GNUPLOT:
15     Gnuplot=arch.Gnuplot
16
17
18
19 #############
20 ## Results ##
21 #############
22
23 class PacketList(BasePacketList):
24     res = []
25     def __init__(self, res=None, name="PacketList", stats=None):
26         """create a packet list from a list of packets
27            res: the list of packets
28            stats: a list of classes that will appear in the stats (defaults to [TCP,UDP,ICMP])"""
29         if stats is None:
30             stats = conf.stats_classic_protocols
31         self.stats = stats
32         if res is None:
33             res = []
34         if isinstance(res, PacketList):
35             res = res.res
36         self.res = res
37         self.listname = name
38     def _elt2pkt(self, elt):
39         return elt
40     def _elt2sum(self, elt):
41         return elt.summary()
42     def _elt2show(self, elt):
43         return self._elt2sum(elt)
44     def __repr__(self):
45 #        stats=dict.fromkeys(self.stats,0) ## needs python >= 2.3  :(
46         stats = dict(map(lambda x: (x,0), self.stats))
47         other = 0
48         for r in self.res:
49             f = 0
50             for p in stats:
51                 if self._elt2pkt(r).haslayer(p):
52                     stats[p] += 1
53                     f = 1
54                     break
55             if not f:
56                 other += 1
57         s = ""
58         ct = conf.color_theme
59         for p in self.stats:
60             s += " %s%s%s" % (ct.packetlist_proto(p.name),
61                               ct.punct(":"),
62                               ct.packetlist_value(stats[p]))
63         s += " %s%s%s" % (ct.packetlist_proto("Other"),
64                           ct.punct(":"),
65                           ct.packetlist_value(other))
66         return "%s%s%s%s%s" % (ct.punct("<"),
67                                ct.packetlist_name(self.listname),
68                                ct.punct(":"),
69                                s,
70                                ct.punct(">"))
71     def __getattr__(self, attr):
72         return getattr(self.res, attr)
73     def __getitem__(self, item):
74         if isinstance(item,type) and issubclass(item,BasePacket):
75             return self.__class__(filter(lambda x: item in self._elt2pkt(x),self.res),
76                                   name="%s from %s"%(item.__name__,self.listname))
77         if type(item) is slice:
78             return self.__class__(self.res.__getitem__(item),
79                                   name = "mod %s" % self.listname)
80         return self.res.__getitem__(item)
81     def __getslice__(self, *args, **kargs):
82         return self.__class__(self.res.__getslice__(*args, **kargs),
83                               name="mod %s"%self.listname)
84     def __add__(self, other):
85         return self.__class__(self.res+other.res,
86                               name="%s+%s"%(self.listname,other.listname))
87     def summary(self, prn=None, lfilter=None):
88         """prints a summary of each packet
89 prn:     function to apply to each packet instead of lambda x:x.summary()
90 lfilter: truth function to apply to each packet to decide whether it will be displayed"""
91         for r in self.res:
92             if lfilter is not None:
93                 if not lfilter(r):
94                     continue
95             if prn is None:
96                 print self._elt2sum(r)
97             else:
98                 print prn(r)
99     def nsummary(self,prn=None, lfilter=None):
100         """prints a summary of each packet with the packet's number
101 prn:     function to apply to each packet instead of lambda x:x.summary()
102 lfilter: truth function to apply to each packet to decide whether it will be displayed"""
103         for i in range(len(self.res)):
104             if lfilter is not None:
105                 if not lfilter(self.res[i]):
106                     continue
107             print conf.color_theme.id(i,fmt="%04i"),
108             if prn is None:
109                 print self._elt2sum(self.res[i])
110             else:
111                 print prn(self.res[i])
112     def display(self): # Deprecated. Use show()
113         """deprecated. is show()"""
114         self.show()
115     def show(self, *args, **kargs):
116         """Best way to display the packet list. Defaults to nsummary() method"""
117         return self.nsummary(*args, **kargs)
118    
119     def filter(self, func):
120         """Returns a packet list filtered by a truth function"""
121         return self.__class__(filter(func,self.res),
122                               name="filtered %s"%self.listname)
123     def make_table(self, *args, **kargs):
124         """Prints a table using a function that returs for each packet its head column value, head row value and displayed value
125         ex: p.make_table(lambda x:(x[IP].dst, x[TCP].dport, x[TCP].sprintf("%flags%")) """
126         return make_table(self.res, *args, **kargs)
127     def make_lined_table(self, *args, **kargs):
128         """Same as make_table, but print a table with lines"""
129         return make_lined_table(self.res, *args, **kargs)
130     def make_tex_table(self, *args, **kargs):
131         """Same as make_table, but print a table with LaTeX syntax"""
132         return make_tex_table(self.res, *args, **kargs)
133
134     def plot(self, f, lfilter=None,**kargs):
135         """Applies a function to each packet to get a value that will be plotted with GnuPlot. A gnuplot object is returned
136         lfilter: a truth function that decides whether a packet must be ploted"""
137         g=Gnuplot.Gnuplot()
138         l = self.res
139         if lfilter is not None:
140             l = filter(lfilter, l)
141         l = map(f,l)
142         g.plot(Gnuplot.Data(l, **kargs))
143         return g
144
145     def diffplot(self, f, delay=1, lfilter=None, **kargs):
146         """diffplot(f, delay=1, lfilter=None)
147         Applies a function to couples (l[i],l[i+delay])"""
148         g = Gnuplot.Gnuplot()
149         l = self.res
150         if lfilter is not None:
151             l = filter(lfilter, l)
152         l = map(f,l[:-delay],l[delay:])
153         g.plot(Gnuplot.Data(l, **kargs))
154         return g
155
156     def multiplot(self, f, lfilter=None, **kargs):
157         """Uses a function that returns a label and a value for this label, then plots all the values label by label"""
158         g=Gnuplot.Gnuplot()
159         l = self.res
160         if lfilter is not None:
161             l = filter(lfilter, l)
162
163         d={}
164         for e in l:
165             k,v = f(e)
166             if k in d:
167                 d[k].append(v)
168             else:
169                 d[k] = [v]
170         data=[]
171         for k in d:
172             data.append(Gnuplot.Data(d[k], title=k, **kargs))
173
174         g.plot(*data)
175         return g
176        
177
178     def rawhexdump(self):
179         """Prints an hexadecimal dump of each packet in the list"""
180         for p in self:
181             hexdump(self._elt2pkt(p))
182
183     def hexraw(self, lfilter=None):
184         """Same as nsummary(), except that if a packet has a Raw layer, it will be hexdumped
185         lfilter: a truth function that decides whether a packet must be displayed"""
186         for i in range(len(self.res)):
187             p = self._elt2pkt(self.res[i])
188             if lfilter is not None and not lfilter(p):
189                 continue
190             print "%s %s %s" % (conf.color_theme.id(i,fmt="%04i"),
191                                 p.sprintf("%.time%"),
192                                 self._elt2sum(self.res[i]))
193             if p.haslayer(conf.raw_layer):
194                 hexdump(p.getlayer(conf.raw_layer).load)
195
196     def hexdump(self, lfilter=None):
197         """Same as nsummary(), except that packets are also hexdumped
198         lfilter: a truth function that decides whether a packet must be displayed"""
199         for i in range(len(self.res)):
200             p = self._elt2pkt(self.res[i])
201             if lfilter is not None and not lfilter(p):
202                 continue
203             print "%s %s %s" % (conf.color_theme.id(i,fmt="%04i"),
204                                 p.sprintf("%.time%"),
205                                 self._elt2sum(self.res[i]))
206             hexdump(p)
207
208     def padding(self, lfilter=None):
209         """Same as hexraw(), for Padding layer"""
210         for i in range(len(self.res)):
211             p = self._elt2pkt(self.res[i])
212             if p.haslayer(Padding):
213                 if lfilter is None or lfilter(p):
214                     print "%s %s %s" % (conf.color_theme.id(i,fmt="%04i"),
215                                         p.sprintf("%.time%"),
216                                         self._elt2sum(self.res[i]))
217                     hexdump(p.getlayer(Padding).load)
218
219     def nzpadding(self, lfilter=None):
220         """Same as padding() but only non null padding"""
221         for i in range(len(self.res)):
222             p = self._elt2pkt(self.res[i])
223             if p.haslayer(Padding):
224                 pad = p.getlayer(Padding).load
225                 if pad == pad[0]*len(pad):
226                     continue
227                 if lfilter is None or lfilter(p):
228                     print "%s %s %s" % (conf.color_theme.id(i,fmt="%04i"),
229                                         p.sprintf("%.time%"),
230                                         self._elt2sum(self.res[i]))
231                     hexdump(p.getlayer(Padding).load)
232        
233
234     def conversations(self, getsrcdst=None,**kargs):
235         """Graphes a conversations between sources and destinations and display it
236         (using graphviz and imagemagick)
237         getsrcdst: a function that takes an element of the list and return the source and dest
238                    by defaults, return source and destination IP
239         type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option
240         target: filename or redirect. Defaults pipe to Imagemagick's display program
241         prog: which graphviz program to use"""
242         if getsrcdst is None:
243             getsrcdst = lambda x:(x['IP'].src, x['IP'].dst)
244         conv = {}
245         for p in self.res:
246             p = self._elt2pkt(p)
247             try:
248                 c = getsrcdst(p)
249             except:
250                 #XXX warning()
251                 continue
252             conv[c] = conv.get(c,0)+1
253         gr = 'digraph "conv" {\n'
254         for s,d in conv:
255             gr += '\t "%s" -> "%s"\n' % (s,d)
256         gr += "}\n"       
257         return do_graph(gr, **kargs)
258
259     def afterglow(self, src=None, event=None, dst=None, **kargs):
260         """Experimental clone attempt of http://sourceforge.net/projects/afterglow
261         each datum is reduced as src -> event -> dst and the data are graphed.
262         by default we have IP.src -> IP.dport -> IP.dst"""
263         if src is None:
264             src = lambda x: x['IP'].src
265         if event is None:
266             event = lambda x: x['IP'].dport
267         if dst is None:
268             dst = lambda x: x['IP'].dst
269         sl = {}
270         el = {}
271         dl = {}
272         for i in self.res:
273             try:
274                 s,e,d = src(i),event(i),dst(i)
275                 if s in sl:
276                     n,l = sl[s]
277                     n += 1
278                     if e not in l:
279                         l.append(e)
280                     sl[s] = (n,l)
281                 else:
282                     sl[s] = (1,[e])
283                 if e in el:
284                     n,l = el[e]
285                     n+=1
286                     if d not in l:
287                         l.append(d)
288                     el[e] = (n,l)
289                 else:
290                     el[e] = (1,[d])
291                 dl[d] = dl.get(d,0)+1
292             except:
293                 continue
294
295         import math
296         def normalize(n):
297             return 2+math.log(n)/4.0
298
299         def minmax(x):
300             m,M = min(x),max(x)
301             if m == M:
302                 m = 0
303             if M == 0:
304                 M = 1
305             return m,M
306
307         mins,maxs = minmax(map(lambda (x,y): x, sl.values()))
308         mine,maxe = minmax(map(lambda (x,y): x, el.values()))
309         mind,maxd = minmax(dl.values())
310    
311         gr = 'digraph "afterglow" {\n\tedge [len=2.5];\n'
312
313         gr += "# src nodes\n"
314         for s in sl:
315             n,l = sl[s]; n = 1+float(n-mins)/(maxs-mins)
316             gr += '"src.%s" [label = "%s", shape=box, fillcolor="#FF0000", style=filled, fixedsize=1, height=%.2f,width=%.2f];\n' % (`s`,`s`,n,n)
317         gr += "# event nodes\n"
318         for e in el:
319             n,l = el[e]; n = n = 1+float(n-mine)/(maxe-mine)
320             gr += '"evt.%s" [label = "%s", shape=circle, fillcolor="#00FFFF", style=filled, fixedsize=1, height=%.2f, width=%.2f];\n' % (`e`,`e`,n,n)
321         for d in dl:
322             n = dl[d]; n = n = 1+float(n-mind)/(maxd-mind)
323             gr += '"dst.%s" [label = "%s", shape=triangle, fillcolor="#0000ff", style=filled, fixedsize=1, height=%.2f, width=%.2f];\n' % (`d`,`d`,n,n)
324
325         gr += "###\n"
326         for s in sl:
327             n,l = sl[s]
328             for e in l:
329                 gr += ' "src.%s" -> "evt.%s";\n' % (`s`,`e`)
330         for e in el:
331             n,l = el[e]
332             for d in l:
333                 gr += ' "evt.%s" -> "dst.%s";\n' % (`e`,`d`)
334            
335         gr += "}"
336         open("/tmp/aze","w").write(gr)
337         return do_graph(gr, **kargs)
338
339
340     def _dump_document(self, **kargs):
341         import pyx
342         d = pyx.document.document()
343         l = len(self.res)
344         for i in range(len(self.res)):
345             elt = self.res[i]
346             c = self._elt2pkt(elt).canvas_dump(**kargs)
347             cbb = c.bbox()
348             c.text(cbb.left(),cbb.top()+1,r"\font\cmssfont=cmss12\cmssfont{Frame %i/%i}" % (i,l),[pyx.text.size.LARGE])
349             if conf.verb >= 2:
350                 os.write(1,".")
351             d.append(pyx.document.page(c, paperformat=pyx.document.paperformat.A4,
352                                        margin=1*pyx.unit.t_cm,
353                                        fittosize=1))
354         return d
355                      
356                  
357
358     def psdump(self, filename = None, **kargs):
359         """Creates a multipage poscript file with a psdump of every packet
360         filename: name of the file to write to. If empty, a temporary file is used and
361                   conf.prog.psreader is called"""
362         d = self._dump_document(**kargs)
363         if filename is None:
364             filename = "/tmp/scapy.psd.%i" % os.getpid()
365             d.writePSfile(filename)
366             os.system("%s %s.ps &" % (conf.prog.psreader,filename))
367         else:
368             d.writePSfile(filename)
369         print
370        
371     def pdfdump(self, filename = None, **kargs):
372         """Creates a PDF file with a psdump of every packet
373         filename: name of the file to write to. If empty, a temporary file is used and
374                   conf.prog.pdfreader is called"""
375         d = self._dump_document(**kargs)
376         if filename is None:
377             filename = "/tmp/scapy.psd.%i" % os.getpid()
378             d.writePDFfile(filename)
379             os.system("%s %s.pdf &" % (conf.prog.pdfreader,filename))
380         else:
381             d.writePDFfile(filename)
382         print
383
384     def sr(self,multi=0):
385         """sr([multi=1]) -> (SndRcvList, PacketList)
386         Matches packets in the list and return ( (matched couples), (unmatched packets) )"""
387         remain = self.res[:]
388         sr = []
389         i = 0
390         while i < len(remain):
391             s = remain[i]
392             j = i
393             while j < len(remain)-1:
394                 j += 1
395                 r = remain[j]
396                 if r.answers(s):
397                     sr.append((s,r))
398                     if multi:
399                         remain[i]._answered=1
400                         remain[j]._answered=2
401                         continue
402                     del(remain[j])
403                     del(remain[i])
404                     i -= 1
405                     break
406             i += 1
407         if multi:
408             remain = filter(lambda x:not hasattr(x,"_answered"), remain)
409         return SndRcvList(sr),PacketList(remain)
410        
411
412
413 class SndRcvList(PacketList):
414     def __init__(self, res=None, name="Results", stats=None):
415         PacketList.__init__(self, res, name, stats)
416     def _elt2pkt(self, elt):
417         return elt[1]
418     def _elt2sum(self, elt):
419         return "%s ==> %s" % (elt[0].summary(),elt[1].summary())
420
421
422
423    
424
425        
426                                                                                
Note: See TracBrowser for help on using the browser.