• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Guillaume Valadon <guillaume@valadon.net>
2
3"""
4Scapy *BSD native support - BPF sockets
5"""
6
7import errno
8import fcntl
9import os
10from select import select
11import struct
12import time
13
14from scapy.arch.bpf.core import get_dev_bpf, attach_filter
15from scapy.arch.bpf.consts import BIOCGBLEN, BIOCGDLT, BIOCGSTATS, \
16    BIOCIMMEDIATE, BIOCPROMISC, BIOCSBLEN, BIOCSETIF, BIOCSHDRCMPLT, \
17    BPF_BUFFER_LENGTH
18from scapy.config import conf
19from scapy.consts import FREEBSD, NETBSD
20from scapy.data import ETH_P_ALL
21from scapy.error import Scapy_Exception, warning
22from scapy.supersocket import SuperSocket
23from scapy.compat import raw
24
25
26if FREEBSD or NETBSD:
27    BPF_ALIGNMENT = 8  # sizeof(long)
28else:
29    BPF_ALIGNMENT = 4  # sizeof(int32_t)
30
31
32# SuperSockets definitions
33
34class _L2bpfSocket(SuperSocket):
35    """"Generic Scapy BPF Super Socket"""
36
37    desc = "read/write packets using BPF"
38    assigned_interface = None
39    fd_flags = None
40    ins = None
41    closed = False
42
43    def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None, nofilter=0):
44
45        # SuperSocket mandatory variables
46        if promisc is None:
47            self.promisc = conf.sniff_promisc
48        else:
49            self.promisc = promisc
50
51        if iface is None:
52            self.iface = conf.iface
53        else:
54            self.iface = iface
55
56        # Get the BPF handle
57        (self.ins, self.dev_bpf) = get_dev_bpf()
58        self.outs = self.ins
59
60        # Set the BPF buffer length
61        try:
62            fcntl.ioctl(self.ins, BIOCSBLEN, struct.pack('I', BPF_BUFFER_LENGTH))
63        except IOError:
64            raise Scapy_Exception("BIOCSBLEN failed on /dev/bpf%i" %
65                                  self.dev_bpf)
66
67        # Assign the network interface to the BPF handle
68        try:
69            fcntl.ioctl(self.ins, BIOCSETIF, struct.pack("16s16x", self.iface.encode()))
70        except IOError:
71            raise Scapy_Exception("BIOCSETIF failed on %s" % self.iface)
72        self.assigned_interface = self.iface
73
74        # Set the interface into promiscuous
75        if self.promisc:
76            self.set_promisc(1)
77
78        # Don't block on read
79        try:
80            fcntl.ioctl(self.ins, BIOCIMMEDIATE, struct.pack('I', 1))
81        except IOError:
82            raise Scapy_Exception("BIOCIMMEDIATE failed on /dev/bpf%i" %
83                                  self.dev_bpf)
84
85        # Scapy will provide the link layer source address
86        # Otherwise, it is written by the kernel
87        try:
88            fcntl.ioctl(self.ins, BIOCSHDRCMPLT, struct.pack('i', 1))
89        except IOError:
90            raise Scapy_Exception("BIOCSHDRCMPLT failed on /dev/bpf%i" %
91                                  self.dev_bpf)
92
93        # Configure the BPF filter
94        if not nofilter:
95            if conf.except_filter:
96                if filter:
97                    filter = "(%s) and not (%s)" % (filter, conf.except_filter)
98                else:
99                    filter = "not (%s)" % conf.except_filter
100            if filter is not None:
101                attach_filter(self.ins, self.iface, filter)
102
103        # Set the guessed packet class
104        self.guessed_cls = self.guess_cls()
105
106    def set_promisc(self, value):
107        """Set the interface in promiscuous mode"""
108
109        try:
110            fcntl.ioctl(self.ins, BIOCPROMISC, struct.pack('i', value))
111        except IOError:
112            raise Scapy_Exception("Cannot set promiscuous mode on interface "
113                                  "(%s)!" % self.iface)
114
115    def __del__(self):
116        """Close the file descriptor on delete"""
117        # When the socket is deleted on Scapy exits, __del__ is
118        # sometimes called "too late", and self is None
119        if self is not None:
120            self.close()
121
122    def guess_cls(self):
123        """Guess the packet class that must be used on the interface"""
124
125        # Get the data link type
126        try:
127            ret = fcntl.ioctl(self.ins, BIOCGDLT, struct.pack('I', 0))
128            ret = struct.unpack('I', ret)[0]
129        except IOError:
130            cls = conf.default_l2
131            warning("BIOCGDLT failed: unable to guess type. Using %s !",
132                    cls.name)
133            return cls
134
135        # Retrieve the corresponding class
136        try:
137            return conf.l2types[ret]
138        except KeyError:
139            cls = conf.default_l2
140            warning("Unable to guess type (type %i). Using %s", ret, cls.name)
141
142    def set_nonblock(self, set_flag=True):
143        """Set the non blocking flag on the socket"""
144
145        # Get the current flags
146        if self.fd_flags is None:
147            try:
148                self.fd_flags = fcntl.fcntl(self.ins, fcntl.F_GETFL)
149            except IOError:
150                warning("Cannot get flags on this file descriptor !")
151                return
152
153        # Set the non blocking flag
154        if set_flag:
155            new_fd_flags = self.fd_flags | os.O_NONBLOCK
156        else:
157            new_fd_flags = self.fd_flags & ~os.O_NONBLOCK
158
159        try:
160            fcntl.fcntl(self.ins, fcntl.F_SETFL, new_fd_flags)
161            self.fd_flags = new_fd_flags
162        except:
163            warning("Can't set flags on this file descriptor !")
164
165    def get_stats(self):
166        """Get received / dropped statistics"""
167
168        try:
169            ret = fcntl.ioctl(self.ins, BIOCGSTATS, struct.pack("2I", 0, 0))
170            return struct.unpack("2I", ret)
171        except IOError:
172            warning("Unable to get stats from BPF !")
173            return (None, None)
174
175    def get_blen(self):
176        """Get the BPF buffer length"""
177
178        try:
179            ret = fcntl.ioctl(self.ins, BIOCGBLEN, struct.pack("I", 0))
180            return struct.unpack("I", ret)[0]
181        except IOError:
182            warning("Unable to get the BPF buffer length")
183            return
184
185    def fileno(self):
186        """Get the underlying file descriptor"""
187        return self.ins
188
189    def close(self):
190        """Close the Super Socket"""
191
192        if not self.closed and self.ins is not None:
193            os.close(self.ins)
194            self.closed = True
195            self.ins = None
196
197    def send(self, x):
198        """Dummy send method"""
199        raise Exception("Can't send anything with %s" % self.__name__)
200
201    def recv(self, x=BPF_BUFFER_LENGTH):
202        """Dummy recv method"""
203        raise Exception("Can't recv anything with %s" % self.__name__)
204
205
206class L2bpfListenSocket(_L2bpfSocket):
207    """"Scapy L2 BPF Listen Super Socket"""
208
209    received_frames = []
210
211    def buffered_frames(self):
212        """Return the number of frames in the buffer"""
213        return len(self.received_frames)
214
215    def get_frame(self):
216        """Get a frame or packet from the received list"""
217        if self.received_frames:
218            return self.received_frames.pop(0)
219
220    @staticmethod
221    def bpf_align(bh_h, bh_c):
222        """Return the index to the end of the current packet"""
223
224        # from <net/bpf.h>
225        return ((bh_h + bh_c)+(BPF_ALIGNMENT-1)) & ~(BPF_ALIGNMENT-1)
226
227    def extract_frames(self, bpf_buffer):
228        """Extract all frames from the buffer and stored them in the received list."""
229
230        # Ensure that the BPF buffer contains at least the header
231        len_bb = len(bpf_buffer)
232        if len_bb < 20:  # Note: 20 == sizeof(struct bfp_hdr)
233            return
234
235        # Extract useful information from the BPF header
236        if FREEBSD or NETBSD:
237            # struct bpf_xhdr or struct bpf_hdr32
238            bh_tstamp_offset = 16
239        else:
240            # struct bpf_hdr
241            bh_tstamp_offset = 8
242
243        # Parse the BPF header
244        bh_caplen = struct.unpack('I', bpf_buffer[bh_tstamp_offset:bh_tstamp_offset+4])[0]
245        next_offset = bh_tstamp_offset + 4
246        bh_datalen = struct.unpack('I', bpf_buffer[next_offset:next_offset+4])[0]
247        next_offset += 4
248        bh_hdrlen = struct.unpack('H', bpf_buffer[next_offset:next_offset+2])[0]
249        if bh_datalen == 0:
250            return
251
252        # Get and store the Scapy object
253        frame_str = bpf_buffer[bh_hdrlen:bh_hdrlen+bh_caplen]
254        try:
255            pkt = self.guessed_cls(frame_str)
256        except:
257            if conf.debug_dissector:
258                raise
259            pkt = conf.raw_layer(frame_str)
260        self.received_frames.append(pkt)
261
262        # Extract the next frame
263        end = self.bpf_align(bh_hdrlen, bh_caplen)
264        if (len_bb - end) >= 20:
265            self.extract_frames(bpf_buffer[end:])
266
267    def recv(self, x=BPF_BUFFER_LENGTH):
268        """Receive a frame from the network"""
269
270        if self.buffered_frames():
271            # Get a frame from the buffer
272            return self.get_frame()
273
274        # Get data from BPF
275        try:
276            bpf_buffer = os.read(self.ins, x)
277        except EnvironmentError as exc:
278            if exc.errno != errno.EAGAIN:
279                warning("BPF recv()", exc_info=True)
280            return
281
282        # Extract all frames from the BPF buffer
283        self.extract_frames(bpf_buffer)
284        return self.get_frame()
285
286
287class L2bpfSocket(L2bpfListenSocket):
288    """"Scapy L2 BPF Super Socket"""
289
290    def send(self, x):
291        """Send a frame"""
292        return os.write(self.outs, raw(x))
293
294    def nonblock_recv(self):
295        """Non blocking receive"""
296
297        if self.buffered_frames():
298            # Get a frame from the buffer
299            return self.get_frame()
300
301        # Set the non blocking flag, read from the socket, and unset the flag
302        self.set_nonblock(True)
303        pkt = L2bpfListenSocket.recv(self)
304        self.set_nonblock(False)
305        return pkt
306
307
308class L3bpfSocket(L2bpfSocket):
309
310    def get_frame(self):
311        """Get a frame or packet from the received list"""
312        pkt = super(L3bpfSocket, self).get_frame()
313        if pkt is not None:
314            return pkt.payload
315
316    def send(self, pkt):
317        """Send a packet"""
318
319        # Use the routing table to find the output interface
320        iff = pkt.route()[0]
321        if iff is None:
322            iff = conf.iface
323
324        # Assign the network interface to the BPF handle
325        if self.assigned_interface != iff:
326            try:
327                fcntl.ioctl(self.outs, BIOCSETIF, struct.pack("16s16x", iff.encode()))
328            except IOError:
329                raise Scapy_Exception("BIOCSETIF failed on %s" % iff)
330            self.assigned_interface = iff
331
332        # Build the frame
333        frame = raw(self.guessed_cls()/pkt)
334        pkt.sent_time = time.time()
335
336        # Send the frame
337        L2bpfSocket.send(self, frame)
338
339
340# Sockets manipulation functions
341
342def isBPFSocket(obj):
343    """Return True is obj is a BPF Super Socket"""
344    return isinstance(obj, L2bpfListenSocket) or isinstance(obj, L2bpfListenSocket) or isinstance(obj, L3bpfSocket)
345
346
347def bpf_select(fds_list, timeout=None):
348    """A call to recv() can return several frames. This functions hides the fact
349       that some frames are read from the internal buffer."""
350
351    # Check file descriptors types
352    bpf_scks_buffered = list()
353    select_fds = list()
354
355    for tmp_fd in fds_list:
356
357        # Specific BPF sockets: get buffers status
358        if isBPFSocket(tmp_fd) and tmp_fd.buffered_frames():
359            bpf_scks_buffered.append(tmp_fd)
360            continue
361
362        # Regular file descriptors or empty BPF buffer
363        select_fds.append(tmp_fd)
364
365    if select_fds:
366        # Call select for sockets with empty buffers
367        if timeout is None:
368            timeout = 0.05
369        ready_list, _, _ = select(select_fds, [], [], timeout)
370        return bpf_scks_buffered + ready_list
371    else:
372        return bpf_scks_buffered
373