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

Revision 4866, 12.5 kB (checked in by ignotus, 4 years ago)

Added full SLL support to umit.zion.scan.sniff package.

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):
37        """
38        """
39        self._raw = buffer
40
41    def __str__(self):
42        """
43        """
44        s = ['+ Frame']
45        raw = struct.unpack('B' * len(self._raw), self._raw)
46        col = 16
47
48        for i in range(len(raw) / col):
49            s.append('| ' + '%.2x ' * col % (raw[i * col: (i + 1) * col]))
50
51        rest = len(raw) % col
52        i = len(raw) / col
53        if rest:
54            s.append('| ' + '%.2x ' * rest % (raw[i * col: i * col + rest]))
55
56        s[-1] = s[-1].replace('| ', '|_')
57
58        return '\n'.join(s)
59
60class SLL(Frame):
61    """
62    """
63    # FIXME: seems that SLL protocol allow headers bigger than 16 bytes. So, a
64    # complete disassemble is needed to avoid problems.
65    header_size = 16
66    header_format = '>HHH8BH'
67
68    def __init__(self, buffer):
69        """
70        """
71        super(SLL, self).__init__(buffer)
72        self.payload = self._raw[self.header_size:]
73        self._fields = struct.unpack(self.header_format,
74                self._raw[:self.header_size])
75
76        # Assuming structure found in: `libpcap/pcap/sll.h,v 1.3'
77
78        self.pkttype = self._fields[0]
79        self.hatype = self._fields[1]
80        self.halen = self._fields[2]
81        self.addr = ':'.join(['%.2x'] * 8) % (self._fields[3:11])
82        self.protocol = self._fields[11]
83
84    def __str__(self):
85        """
86        """
87        s = ['+ SLL']
88        s.append('| (pkttype 0x%.4x)' % self.pkttype)
89        s.append('| (hatype 0x%.4x)' % self.hatype)
90        s.append('| (halen 0x%.4x)' % self.halen)
91        s.append('| (addr %s)' % self.addr)
92        s.append('|_(protocol 0x%.4x)' % self.protocol)
93
94        return "\n".join(s)
95
96class Ethernet(Frame):
97    """
98    """
99    header_size = 14
100    header_format = '>6B6BH'
101
102    def __init__(self, buffer):
103        """
104        """
105        super(Ethernet, self).__init__(buffer)
106        self.payload = self._raw[self.header_size:]
107        self._fields = struct.unpack(self.header_format,
108                self._raw[:self.header_size])
109
110        self.dst = ':'.join(['%.2x'] * 6) % (self._fields[0:6])
111        self.src = ':'.join(['%.2x'] * 6) % (self._fields[6:12])
112        self.type = self._fields[12]
113
114    def __str__(self):
115        """
116        """
117        s = ['+ Ethernet']
118        s.append('| (dst %s)' % self.dst)
119        s.append('| (src %s)' % self.src)
120        s.append('|_(type 0x%.4x)' % self.type)
121
122        return '\n'.join(s)
123
124class IPv4(Frame):
125    """
126    """
127    header_size = 20
128    header_format = '>BBHHHBBH4B4B'
129
130    def __init__(self, buffer):
131        """
132        """
133        super(IPv4, self).__init__(buffer)
134        self._fields = struct.unpack(self.header_format,
135                self._raw[:self.header_size])
136
137        self.version = self._fields[0] >> 4
138        self.hdr_len = self._fields[0] & 0x0F
139        self.tos = self._fields[1]
140        self.len = self._fields[2]
141        self.id = self._fields[3]
142        self.flags = (self._fields[4] & 0xE000) >> 13
143        self.frag_offset = (self._fields[4] & 0x1fff)
144        self.ttl = self._fields[5]
145        self.proto = self._fields[6]
146        self.checksum = self._fields[7]
147        self.src = '%d.%d.%d.%d' % (self._fields[8:12])
148        self.dst = '%d.%d.%d.%d' % (self._fields[12:16])
149
150        # FIXME: verify header size
151        self.payload = self._raw[self.header_size:]
152
153    def __str__(self):
154        """
155        """
156        s = ['+ IPv4']
157        s.append('| (version 0x%x)' % self.version)
158        s.append('| (hdr_len 0x%x)' % self.hdr_len)
159        s.append('| (tos 0x%.2x)' % self.tos)
160        s.append('| (len 0x%.4x)' % self.len)
161        s.append('| (id 0x%.4x)' % self.id)
162        s.append('| (flags 0x%x)' % self.flags)
163        s.append('| (frag_offset 0x%.4x)' % self.frag_offset)
164        s.append('| (ttl 0x%.2x)' % self.ttl)
165        s.append('| (proto 0x%.2x)' % self.proto)
166        s.append('| (checksum 0x%.2x)' % self.checksum)
167        s.append('| (src %s)' % self.src)
168        s.append('|_(dst %s)' % self.dst)
169
170        return '\n'.join(s)
171
172class IPv6(Frame):
173    """
174    """
175    header_size = 40
176    header_format = '>IHBB8H8H'
177
178    def __init__(self, buffer):
179        """
180        """
181        super(IPv6, self).__init__(buffer)
182        self.payload = self._raw[self.header_size:]
183        self._fields = struct.unpack(self.header_format,
184                self._raw[:self.header_size])
185
186        self.version = self._fields[0] >> 28
187        self.clas = (self._fields[0] & 0x0FF00000) >> 8 # Should be self.class
188        self.flow = (self._fields[0] & 0x000FFFFF)
189        self.plen = self._fields[1]
190        self.nxt = self._fields[2]
191        self.hlim = self._fields[3]
192        self.dst = ':'.join(["%.4x"] * 8) % (self._fields[4:12])
193        self.src = ':'.join(["%.4x"] * 8) % (self._fields[12:20])
194
195    def __str__(self):
196        """
197        """
198        s = ['+ IPv6']
199        s.append('| (version 0x%x)' % self.version)
200        s.append('| (clas 0x%.2x)' % self.clas)
201        s.append('| (flow 0x%.3x)' % self.flow)
202        s.append('| (plen 0x%.4x)' % self.plen)
203        s.append('| (nxt 0x%.2x)' % self.nxt)
204        s.append('| (hlim 0x%.2x)' % self.hlim)
205        s.append('| (src %s)' % self.src)
206        s.append('|_(dst %s)' % self.dst)
207
208        return '\n'.join(s)
209
210class TCP(Frame):
211    """
212    """
213    header_size = 20
214    header_format = '>HHIIBBHHH'
215
216    def __init__(self, buffer):
217        """
218        """
219        super(TCP, self).__init__(buffer)
220        self._fields = struct.unpack(self.header_format,
221                self._raw[:self.header_size])
222
223        self.srcport = self._fields[0]
224        self.dstport = self._fields[1]
225        self.seq = self._fields[2]
226        self.ack = self._fields[3]
227        self.hdr_len = self._fields[4] >> 4
228        self.reserved = self._fields[4] & 0x0F
229        self.flags = self._fields[5]
230        self.window_size = self._fields[6]
231        self.checksum = self._fields[7]
232        self.urgent_pointer = self._fields[8]
233
234        # FIXME: verify header size
235        self.payload = self._raw[self.header_size:]
236
237    def __str__(self):
238        """
239        """
240        s = ['+ TCP']
241
242        s.append('| (srcport %d)' % self.srcport)
243        s.append('| (dstport %d)' % self.dstport)
244        s.append('| (seq %d)' % self.seq)
245        s.append('| (ack %d)' % self.ack)
246        s.append('| (hdr_len 0x%x)' % self.hdr_len)
247        s.append('| (reserved 0x%x)' % self.reserved)
248        s.append('| (flags 0x%.2x)' % self.flags)
249        s.append('| (window_size 0x%.4x)' % self.window_size)
250        s.append('| (checksum 0x%.4x)' % self.checksum)
251        s.append('|_(urgent_pointer 0x%.4x)' % self.urgent_pointer)
252
253        return "\n".join(s)
254
255class Packet(object):
256    """
257    """
258    def __init__(self, timestamp, buffer, linktype):
259        """
260        """
261        self.__timestamp = timestamp
262        self.__linktype = linktype
263        self.__buffer = buffer
264        self.__packet = None
265
266    def get_timestamp(self):
267        """
268        """
269        return self.__timestamp
270
271    def get_packet(self):
272        """
273        """
274        return self.__packet
275
276    def disassemble(self):
277        """
278        """
279        self.__packet = []
280        next_type = None
281
282        # Disassembling first frame.
283        # There are two options to make this work right:
284        #   1. Disassembly the link layer and see what the next protocol on
285        #      their data fields;
286        #   2. Ignore the first `n' bytes of link layer data. Where `n' stands
287        #      for size of link layer protocol header.
288        # The second one can fail when the link layer protocol has a variable
289        # header length. So, is not correct do this way. Moreover, we can be
290        # sure what is the next protocol without looking at link layer header.
291        # For while we will assume that IP is next packet when the link layer
292        # disassembling is not implemented
293        if self.__linktype == pcap.DLT_EN10MB:
294            e = Ethernet(self.__buffer)
295            self.__packet.append(e)
296            next_type = e.type
297        elif self.__linktype == pcap.DLT_LINUX_SLL:
298            s = SLL(self.__buffer)
299            self.__packet.append(s)
300            # Bytes 15 and 16 of SLL says the next protocol.
301            next_type = s.protocol
302        else:
303            # If the link layer type is unknown return all payload as a base
304            # frame.
305            f = Frame(self.__buffer)
306            self.__packet.append(f)
307            return self.__packet
308
309        # Disassembling second frame.
310        # For a while we are using hexadecimal of Ethernet a SLL next protocol
311        # fields to identify next frames. Maybe this can be changed for keep
312        # compatibility with other link layer protocols.
313        if next_type == 0x0800:
314            i = IPv4(self.__packet[-1].payload)
315            self.__packet.append(i)
316            next_type = i.proto
317        elif next_type == 0x86DD:
318            i = IPv6(self.__packet[-1].payload)
319            self.__packet.append(i)
320            next_type = i.nxt
321        else:
322            f = Frame(self.__packet[-1].payload)
323            self.__packet.append(f)
324            return self.__packet
325
326        # Disassembling third frame.
327        # For a while we are using IPv4 protocol and IPv6 next header fields to
328        # check what the next protocol header.
329        if next_type == 0x06:
330            t = TCP(self.__packet[-1].payload)
331            self.__packet.append(t)
332        else:
333            f = Frame(self.__packet[-1].payload)
334            self.__packet.append(f)
335            return self.__packet
336
337        if len(self.__packet[-1].payload):
338            f = Frame(self.__packet[-1].payload)
339            self.__packet.append(f)
340
341        return self.__packet
342
343    def get_field(self, field):
344        """
345        """
346        proto, attr = field.split('.')
347
348        for p in self.__packet:
349            if proto == 'ether' and type(p) == Ethernet and hasattr(p, attr):
350                return getattr(p, attr)
351            elif proto == 'ipv4' and type(p) == IPv4 and hasattr(p, attr):
352                return getattr(p, attr)
353            elif proto == 'ipv6' and type(p) == IPv6 and hasattr(p, attr):
354                return getattr(p, attr)
355            elif proto == 'tcp' and type(p) == TCP and hasattr(p, attr):
356                return getattr(p, attr)
357
358        return None
359
360    def __str__(self):
361        """
362        """
363        s = ['timestamp %f' % self.__timestamp]
364
365        for p in self.__packet:
366            s.append(p.__str__())
367
368        return '\n'.join(s)
369
370class Device(object):
371    """
372    """
373    def __init__(self, name):
374        """
375        """
376        self.name = name
377        try:
378            self.naddr, self.mask = pcap.lookupnet(self.name)
379        except:
380            self.naddr, self.mask = None, None
381
382class Sniff(object):
383    """
384    """
385    def __init__(self):
386        """
387        """
388        self.amount = None
389        self.filter = ''
390        self.fields = []
391        self.packets = []
392        self.devices = {}
393        for d in umpa.sniffing.get_available_devices():
394            self.devices[d] = Device(d)
395
396    def start(self, device):
397        """
398        """
399        capture = pcap.pcap(device)
400        capture.setfilter(self.filter)
401        number = 0
402
403        for timestamp, packet in capture:
404            p = Packet(timestamp, packet, capture.datalink())
405            p.disassemble()
406            self.packets.append(p)
407            number += 1
408
409            if len(self.fields):
410                s = []
411                for f in self.fields:
412                    s.append(str(p.get_field(f)))
413                print '(timestamp %f)' % p.get_timestamp(),', '.join(s)
414            else:
415                print '\n', p
416
417            if number == self.amount:
418                break
Note: See TracBrowser for help on using the browser.