root/branch/zion/umit/zion/scan/sniff.py @ 4864

Revision 4864, 11.6 kB (checked in by ignotus, 4 years ago)

Improved sniffing support to allow amount couting, filtering and selective
data.

Line 
1# vim: set encoding=utf-8 :
2
3# Copyright (C) 2009 Adriano Monteiro Marques.
4#
5# Author: Joao Paulo de Souza Medeiros <ignotus@umitproject.org>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21"""
22"""
23
24import pcap
25import socket
26import struct
27
28import umpa.sniffing
29
30class Frame(object):
31    """
32    """
33    header_size = None   # Base header size.
34    header_format = None
35
36    def __init__(self, buffer, size=None):
37        """
38        """
39        self._raw = buffer
40
41        if size:
42            self.header_size = size
43            self.payload = self._raw[self.header_size:]
44
45    def __str__(self):
46        """
47        """
48        s = ["+ Frame"]
49        raw = struct.unpack('B' * len(self._raw), self._raw)
50        col = 16
51
52        for i in range(len(raw) / col):
53            s.append("| " + "%.2x " * col % (raw[i * col: (i + 1) * col]))
54
55        rest = len(raw) % col
56        i = len(raw) / col
57        if rest:
58            s.append("| " + "%.2x " * rest % (raw[i * col: i * col + rest]))
59
60        s[-1] = s[-1].replace("| ", "|_")
61
62        return "\n".join(s)
63
64class Ethernet(Frame):
65    """
66    """
67    header_size = 14
68    header_format = '>6B6BH'
69
70    def __init__(self, buffer):
71        """
72        """
73        super(Ethernet, self).__init__(buffer)
74        self.payload = self._raw[self.header_size:]
75        self._fields = struct.unpack(self.header_format,
76                self._raw[:self.header_size])
77
78        self.dst = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" % (self._fields[0:6])
79        self.src = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" % (self._fields[6:12])
80        self.type = self._fields[12]
81
82    def __str__(self):
83        """
84        """
85        s = ["+ Ethernet"]
86        s.append("| (dst %s)" % self.dst)
87        s.append("| (src %s)" % self.src)
88        s.append("|_(type 0x%.4x)" % self.type)
89
90        return "\n".join(s)
91
92class IPv4(Frame):
93    """
94    """
95    header_size = 20
96    header_format = '>BBHHHBBH4B4B'
97
98    def __init__(self, buffer):
99        """
100        """
101        super(IPv4, self).__init__(buffer)
102        self._fields = struct.unpack(self.header_format,
103                self._raw[:self.header_size])
104
105        self.version = self._fields[0] >> 4
106        self.hdr_len = self._fields[0] & 0x0F
107        self.tos = self._fields[1]
108        self.len = self._fields[2]
109        self.id = self._fields[3]
110        self.flags = (self._fields[4] & 0xE000) >> 13
111        self.frag_offset = (self._fields[4] & 0x1fff)
112        self.ttl = self._fields[5]
113        self.proto = self._fields[6]
114        self.checksum = self._fields[7]
115        self.src = "%d.%d.%d.%d" % (self._fields[8:12])
116        self.dst = "%d.%d.%d.%d" % (self._fields[12:16])
117
118        # FIXME: verify header size
119        self.payload = self._raw[self.header_size:]
120
121    def __str__(self):
122        """
123        """
124        s = ["+ IPv4"]
125        s.append("| (version 0x%x)" % self.version)
126        s.append("| (hdr_len 0x%x)" % self.hdr_len)
127        s.append("| (tos 0x%.2x)" % self.tos)
128        s.append("| (len 0x%.4x)" % self.len)
129        s.append("| (id 0x%.4x)" % self.id)
130        s.append("| (flags 0x%x)" % self.flags)
131        s.append("| (frag_offset 0x%.4x)" % self.frag_offset)
132        s.append("| (ttl 0x%.2x)" % self.ttl)
133        s.append("| (proto 0x%.2x)" % self.proto)
134        s.append("| (checksum 0x%.2x)" % self.checksum)
135        s.append("| (src %s)" % self.src)
136        s.append("|_(dst %s)" % self.dst)
137
138        return "\n".join(s)
139
140class IPv6(Frame):
141    """
142    """
143    header_size = 40
144    header_format = '>IHBB8H8H'
145
146    def __init__(self, buffer):
147        """
148        """
149        super(IPv6, self).__init__(buffer)
150        self.payload = self._raw[self.header_size:]
151        self._fields = struct.unpack(self.header_format,
152                self._raw[:self.header_size])
153
154        self.version = self._fields[0] >> 28
155        self.clas = (self._fields[0] & 0x0FF00000) >> 8 # Should be self.class
156        self.flow = (self._fields[0] & 0x000FFFFF)
157        self.plen = self._fields[1]
158        self.nxt = self._fields[2]
159        self.hlim = self._fields[3]
160        self.dst = ":".join(["%.4x"] * 8) % (self._fields[4:12])
161        self.src = ":".join(["%.4x"] * 8) % (self._fields[12:20])
162
163    def __str__(self):
164        """
165        """
166        s = ["+ IPv6"]
167        s.append("| (version 0x%x)" % self.version)
168        s.append("| (clas 0x%.2x)" % self.clas)
169        s.append("| (flow 0x%.3x)" % self.flow)
170        s.append("| (plen 0x%.4x)" % self.plen)
171        s.append("| (nxt 0x%.2x)" % self.nxt)
172        s.append("| (hlim 0x%.2x)" % self.hlim)
173        s.append("| (src %s)" % self.src)
174        s.append("|_(dst %s)" % self.dst)
175
176        return "\n".join(s)
177
178class TCP(Frame):
179    """
180    """
181    header_size = 20
182    header_format = '>HHIIBBHHH'
183
184    def __init__(self, buffer):
185        """
186        """
187        super(TCP, self).__init__(buffer)
188        self._fields = struct.unpack(self.header_format,
189                self._raw[:self.header_size])
190
191        self.srcport = self._fields[0]
192        self.dstport = self._fields[1]
193        self.seq = self._fields[2]
194        self.ack = self._fields[3]
195        self.hdr_len = self._fields[4] >> 4
196        self.reserved = self._fields[4] & 0x0F
197        self.flags = self._fields[5]
198        self.window_size = self._fields[6]
199        self.checksum = self._fields[7]
200        self.urgent_pointer = self._fields[8]
201
202        # FIXME: verify header size
203        self.payload = self._raw[self.header_size:]
204
205    def __str__(self):
206        """
207        """
208        s = ["+ TCP"]
209
210        s.append("| (srcport %d)" % self.srcport)
211        s.append("| (dstport %d)" % self.dstport)
212        s.append("| (seq %d)" % self.seq)
213        s.append("| (ack %d)" % self.ack)
214        s.append("| (hdr_len 0x%x)" % self.hdr_len)
215        s.append("| (reserved 0x%x)" % self.reserved)
216        s.append("| (flags 0x%.2x)" % self.flags)
217        s.append("| (window_size 0x%.4x)" % self.window_size)
218        s.append("| (checksum 0x%.4x)" % self.checksum)
219        s.append("|_(urgent_pointer 0x%.4x)" % self.urgent_pointer)
220
221        return "\n".join(s)
222
223class Packet(object):
224    """
225    """
226    def __init__(self, timestamp, buffer, linktype):
227        """
228        """
229        self.__timestamp = timestamp
230        self.__linktype = linktype
231        self.__buffer = buffer
232        self.__packet = None
233
234    def get_packet(self):
235        """
236        """
237        return self.__packet
238
239    def disassemble(self):
240        """
241        """
242        self.__packet = []
243        next_type = None
244
245        # Disassembling first frame.
246        # There are two options to make this work right:
247        #   1. Disassembly the link layer and see what the next protocol on
248        #      their data fields;
249        #   2. Ignore the first `n' bytes of link layer data. Where `n' stands
250        #      for size of link layer protocol header.
251        # The second one can fail when the link layer protocol has a variable
252        # header length. So, is not correct do this way. Moreover, we can be
253        # sure what is the next protocol without looking at link layer header.
254        # For while we will assume that IP is next packet when the link layer
255        # disassembling is not implemented
256        if self.__linktype == pcap.DLT_EN10MB:
257            e = Ethernet(self.__buffer)
258            self.__packet.append(e)
259            next_type = e.type
260        elif self.__linktype == pcap.DLT_LINUX_SLL:
261            # FIXME: the SLL protocol allow headers bigger than 16 bytes. So, a
262            # complete disassemble is needed to avoid problems.
263            f = Frame(self.__buffer, 16)
264            self.__packet.append(f)
265            # Bytes 15 and 16 of SLL says the next protocol.
266            next_type = struct.unpack('>H', f._raw[14:16])[0]
267        else:
268            # If the link layer type is unknown return all payload as a base
269            # frame.
270            f = Frame(self.__buffer)
271            self.__packet.append(f)
272            return self.__packet
273
274        # Disassembling second frame.
275        # For a while we are using hexadecimal of Ethernet a SLL next protocol
276        # fields to identify next frames. Maybe this can be changed for keep
277        # compatibility with other link layer protocols.
278        if next_type == 0x0800:
279            i = IPv4(self.__packet[-1].payload)
280            self.__packet.append(i)
281            next_type = i.proto
282        elif next_type == 0x86DD:
283            i = IPv6(self.__packet[-1].payload)
284            self.__packet.append(i)
285            next_type = i.nxt
286        else:
287            f = Frame(self.__packet[-1].payload)
288            self.__packet.append(f)
289            return self.__packet
290
291        # Disassembling third frame.
292        # For a while we are using IPv4 protocol and IPv6 next header fields to
293        # check what the next protocol header.
294        if next_type == 0x06:
295            t = TCP(self.__packet[-1].payload)
296            self.__packet.append(t)
297        else:
298            f = Frame(self.__packet[-1].payload)
299            self.__packet.append(f)
300            return self.__packet
301
302        if len(self.__packet[-1].payload):
303            f = Frame(self.__packet[-1].payload)
304            self.__packet.append(f)
305
306        return self.__packet
307
308    def get_field(self, field):
309        """
310        """
311        proto, attr = field.split('.')
312
313        for p in self.__packet:
314            if proto == 'ether' and type(p) == Ethernet and hasattr(p, attr):
315                return getattr(p, attr)
316            elif proto == 'ipv4' and type(p) == IPv4 and hasattr(p, attr):
317                return getattr(p, attr)
318            elif proto == 'ipv6' and type(p) == IPv6 and hasattr(p, attr):
319                return getattr(p, attr)
320            elif proto == 'tcp' and type(p) == TCP and hasattr(p, attr):
321                return getattr(p, attr)
322
323        return None
324
325    def __str__(self):
326        """
327        """
328        s = [str(self.__timestamp)]
329
330        for p in self.__packet:
331            s.append(p.__str__())
332
333        return '\n'.join(s)
334
335class Device(object):
336    """
337    """
338    def __init__(self, name):
339        """
340        """
341        self.name = name
342        try:
343            self.naddr, self.mask = pcap.lookupnet(self.name)
344        except:
345            self.naddr, self.mask = None, None
346
347class Sniff(object):
348    """
349    """
350    def __init__(self):
351        """
352        """
353        self.amount = None
354        self.filter = ''
355        self.fields = []
356        self.packets = []
357        self.devices = {}
358        for d in umpa.sniffing.get_available_devices():
359            self.devices[d] = Device(d)
360
361    def start(self, device):
362        """
363        """
364        capture = pcap.pcap(device)
365        capture.setfilter(self.filter)
366        number = 0
367
368        for timestamp, packet in capture:
369            p = Packet(timestamp, packet, capture.datalink())
370            p.disassemble()
371            self.packets.append(p)
372            number += 1
373
374            if len(self.fields):
375                s = []
376                for f in self.fields:
377                    s.append(str(p.get_field(f)))
378                print '[%d]' % number, ', '.join(s)
379            else:
380                print '[%d]' % number, p
381
382            if number == self.amount:
383                break
Note: See TracBrowser for help on using the browser.