1# Guillaume Valadon <guillaume@valadon.net>
2
3"""
4Scapy *BSD native support - BPF sockets
5"""
6
7from ctypes import c_long, sizeof
8import errno
9import fcntl
10import os
11import platform
12from select import select
13import struct
14import time
15
16from scapy.arch.bpf.core import get_dev_bpf, attach_filter
17from scapy.arch.bpf.consts import BIOCGBLEN, BIOCGDLT, BIOCGSTATS, \
18    BIOCIMMEDIATE, BIOCPROMISC, BIOCSBLEN, BIOCSETIF, BIOCSHDRCMPLT, \
19    BPF_BUFFER_LENGTH, BIOCSDLT, DLT_IEEE802_11_RADIO
20from scapy.config import conf
21from scapy.consts import FREEBSD, NETBSD, DARWIN
22from scapy.data import ETH_P_ALL
23from scapy.error import Scapy_Exception, warning
24from scapy.interfaces import network_name
25from scapy.supersocket import SuperSocket
26from scapy.compat import raw
27from scapy.layers.l2 import Loopback
28
29
30if FREEBSD:
31    # On 32bit architectures long might be 32bit.
32    BPF_ALIGNMENT = sizeof(c_long)
33elif NETBSD:
34    BPF_ALIGNMENT = 8  # sizeof(long)
35else:
36    BPF_ALIGNMENT = 4  # sizeof(int32_t)
37
38
39# SuperSockets definitions
40
41class _L2bpfSocket(SuperSocket):
42    """"Generic Scapy BPF Super Socket"""
43
44    desc = "read/write packets using BPF"
45    nonblocking_socket = True
46
47    def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None,
48                 nofilter=0, monitor=False):
49        self.fd_flags = None
50        self.assigned_interface = None
51
52        # SuperSocket mandatory variables
53        if promisc is None:
54            self.promisc = conf.sniff_promisc
55        else:
56            self.promisc = promisc
57
58        self.iface = network_name(iface or conf.iface)
59
60        # Get the BPF handle
61        self.ins = None
62        (self.ins, self.dev_bpf) = get_dev_bpf()
63        self.outs = self.ins
64
65        # Set the BPF buffer length
66        try:
67            fcntl.ioctl(self.ins, BIOCSBLEN, struct.pack('I', BPF_BUFFER_LENGTH))  # noqa: E501
68        except IOError:
69            raise Scapy_Exception("BIOCSBLEN failed on /dev/bpf%i" %
70                                  self.dev_bpf)
71
72        # Assign the network interface to the BPF handle
73        try:
74            fcntl.ioctl(self.ins, BIOCSETIF, struct.pack("16s16x", self.iface.encode()))  # noqa: E501
75        except IOError:
76            raise Scapy_Exception("BIOCSETIF failed on %s" % self.iface)
77        self.assigned_interface = self.iface
78
79        # Set the interface into promiscuous
80        if self.promisc:
81            self.set_promisc(1)
82
83        # Set the interface to monitor mode
84        # Note: - trick from libpcap/pcap-bpf.c - monitor_mode()
85        #       - it only works on OS X 10.5 and later
86        if DARWIN and monitor:
87            dlt_radiotap = struct.pack('I', DLT_IEEE802_11_RADIO)
88            try:
89                fcntl.ioctl(self.ins, BIOCSDLT, dlt_radiotap)
90            except IOError:
91                raise Scapy_Exception("Can't set %s into monitor mode!" %
92                                      self.iface)
93
94        # Don't block on read
95        try:
96            fcntl.ioctl(self.ins, BIOCIMMEDIATE, struct.pack('I', 1))
97        except IOError:
98            raise Scapy_Exception("BIOCIMMEDIATE failed on /dev/bpf%i" %
99                                  self.dev_bpf)
100
101        # Scapy will provide the link layer source address
102        # Otherwise, it is written by the kernel
103        try:
104            fcntl.ioctl(self.ins, BIOCSHDRCMPLT, struct.pack('i', 1))
105        except IOError:
106            raise Scapy_Exception("BIOCSHDRCMPLT failed on /dev/bpf%i" %
107                                  self.dev_bpf)
108
109        # Configure the BPF filter
110        filter_attached = False
111        if not nofilter:
112            if conf.except_filter:
113                if filter:
114                    filter = "(%s) and not (%s)" % (filter, conf.except_filter)
115                else:
116                    filter = "not (%s)" % conf.except_filter
117            if filter is not None:
118                try:
119                    attach_filter(self.ins, filter, self.iface)
120                    filter_attached = True
121                except ImportError as ex:
122                    warning("Cannot set filter: %s" % ex)
123        if NETBSD and filter_attached is False:
124            # On NetBSD, a filter must be attached to an interface, otherwise
125            # no frame will be received by os.read(). When no filter has been
126            # configured, Scapy uses a simple tcpdump filter that does nothing
127            # more than ensuring the length frame is not null.
128            filter = "greater 0"
129            try:
130                attach_filter(self.ins, filter, self.iface)
131            except ImportError as ex:
132                warning("Cannot set filter: %s" % ex)
133
134        # Set the guessed packet class
135        self.guessed_cls = self.guess_cls()
136
137    def set_promisc(self, value):
138        """Set the interface in promiscuous mode"""
139
140        try:
141            fcntl.ioctl(self.ins, BIOCPROMISC, struct.pack('i', value))
142        except IOError:
143            raise Scapy_Exception("Cannot set promiscuous mode on interface "
144                                  "(%s)!" % self.iface)
145
146    def __del__(self):
147        """Close the file descriptor on delete"""
148        # When the socket is deleted on Scapy exits, __del__ is
149        # sometimes called "too late", and self is None
150        if self is not None:
151            self.close()
152
153    def guess_cls(self):
154        """Guess the packet class that must be used on the interface"""
155
156        # Get the data link type
157        try:
158            ret = fcntl.ioctl(self.ins, BIOCGDLT, struct.pack('I', 0))
159            ret = struct.unpack('I', ret)[0]
160        except IOError:
161            cls = conf.default_l2
162            warning("BIOCGDLT failed: unable to guess type. Using %s !",
163                    cls.name)
164            return cls
165
166        # Retrieve the corresponding class
167        try:
168            return conf.l2types[ret]
169        except KeyError:
170            cls = conf.default_l2
171            warning("Unable to guess type (type %i). Using %s", ret, cls.name)
172
173    def set_nonblock(self, set_flag=True):
174        """Set the non blocking flag on the socket"""
175
176        # Get the current flags
177        if self.fd_flags is None:
178            try:
179                self.fd_flags = fcntl.fcntl(self.ins, fcntl.F_GETFL)
180            except IOError:
181                warning("Cannot get flags on this file descriptor !")
182                return
183
184        # Set the non blocking flag
185        if set_flag:
186            new_fd_flags = self.fd_flags | os.O_NONBLOCK
187        else:
188            new_fd_flags = self.fd_flags & ~os.O_NONBLOCK
189
190        try:
191            fcntl.fcntl(self.ins, fcntl.F_SETFL, new_fd_flags)
192            self.fd_flags = new_fd_flags
193        except Exception:
194            warning("Can't set flags on this file descriptor !")
195
196    def get_stats(self):
197        """Get received / dropped statistics"""
198
199        try:
200            ret = fcntl.ioctl(self.ins, BIOCGSTATS, struct.pack("2I", 0, 0))
201            return struct.unpack("2I", ret)
202        except IOError:
203            warning("Unable to get stats from BPF !")
204            return (None, None)
205
206    def get_blen(self):
207        """Get the BPF buffer length"""
208
209        try:
210            ret = fcntl.ioctl(self.ins, BIOCGBLEN, struct.pack("I", 0))
211            return struct.unpack("I", ret)[0]
212        except IOError:
213            warning("Unable to get the BPF buffer length")
214            return
215
216    def fileno(self):
217        """Get the underlying file descriptor"""
218        return self.ins
219
220    def close(self):
221        """Close the Super Socket"""
222
223        if not self.closed and self.ins is not None:
224            os.close(self.ins)
225            self.closed = True
226            self.ins = None
227
228    def send(self, x):
229        """Dummy send method"""
230        raise Exception(
231            "Can't send anything with %s" % self.__class__.__name__
232        )
233
234    def recv_raw(self, x=BPF_BUFFER_LENGTH):
235        """Dummy recv method"""
236        raise Exception(
237            "Can't recv anything with %s" % self.__class__.__name__
238        )
239
240    @staticmethod
241    def select(sockets, remain=None):
242        """This function is called during sendrecv() routine to select
243        the available sockets.
244        """
245        # sockets, None (means use the socket's recv() )
246        return bpf_select(sockets, remain)
247
248
249class L2bpfListenSocket(_L2bpfSocket):
250    """"Scapy L2 BPF Listen Super Socket"""
251
252    def __init__(self, *args, **kwargs):
253        self.received_frames = []
254        super(L2bpfListenSocket, self).__init__(*args, **kwargs)
255
256    def buffered_frames(self):
257        """Return the number of frames in the buffer"""
258        return len(self.received_frames)
259
260    def get_frame(self):
261        """Get a frame or packet from the received list"""
262        if self.received_frames:
263            return self.received_frames.pop(0)
264        else:
265            return None, None, None
266
267    @staticmethod
268    def bpf_align(bh_h, bh_c):
269        """Return the index to the end of the current packet"""
270
271        # from <net/bpf.h>
272        return ((bh_h + bh_c) + (BPF_ALIGNMENT - 1)) & ~(BPF_ALIGNMENT - 1)
273
274    def extract_frames(self, bpf_buffer):
275        """Extract all frames from the buffer and stored them in the received list."""  # noqa: E501
276
277        # Ensure that the BPF buffer contains at least the header
278        len_bb = len(bpf_buffer)
279        if len_bb < 20:  # Note: 20 == sizeof(struct bfp_hdr)
280            return
281
282        # Extract useful information from the BPF header
283        if FREEBSD:
284            # Unless we set BIOCSTSTAMP to something different than
285            # BPF_T_MICROTIME, we will get bpf_hdr on FreeBSD, which means
286            # that we'll get a struct timeval, which is time_t, suseconds_t.
287            # On i386 time_t is 32bit so the bh_tstamp will only be 8 bytes.
288            # We really want to set BIOCSTSTAMP to BPF_T_NANOTIME and be
289            # done with this and it always be 16?
290            if platform.machine() == "i386":
291                # struct bpf_hdr
292                bh_tstamp_offset = 8
293            else:
294                # struct bpf_hdr (64bit time_t) or struct bpf_xhdr
295                bh_tstamp_offset = 16
296        elif NETBSD:
297            # struct bpf_hdr or struct bpf_hdr32
298            bh_tstamp_offset = 16
299        else:
300            # struct bpf_hdr
301            bh_tstamp_offset = 8
302
303        # Parse the BPF header
304        bh_caplen = struct.unpack('I', bpf_buffer[bh_tstamp_offset:bh_tstamp_offset + 4])[0]  # noqa: E501
305        next_offset = bh_tstamp_offset + 4
306        bh_datalen = struct.unpack('I', bpf_buffer[next_offset:next_offset + 4])[0]  # noqa: E501
307        next_offset += 4
308        bh_hdrlen = struct.unpack('H', bpf_buffer[next_offset:next_offset + 2])[0]  # noqa: E501
309        if bh_datalen == 0:
310            return
311
312        # Get and store the Scapy object
313        frame_str = bpf_buffer[bh_hdrlen:bh_hdrlen + bh_caplen]
314        self.received_frames.append(
315            (self.guessed_cls, frame_str, None)
316        )
317
318        # Extract the next frame
319        end = self.bpf_align(bh_hdrlen, bh_caplen)
320        if (len_bb - end) >= 20:
321            self.extract_frames(bpf_buffer[end:])
322
323    def recv_raw(self, x=BPF_BUFFER_LENGTH):
324        """Receive a frame from the network"""
325
326        x = min(x, BPF_BUFFER_LENGTH)
327
328        if self.buffered_frames():
329            # Get a frame from the buffer
330            return self.get_frame()
331
332        # Get data from BPF
333        try:
334            bpf_buffer = os.read(self.ins, x)
335        except EnvironmentError as exc:
336            if exc.errno != errno.EAGAIN:
337                warning("BPF recv_raw()", exc_info=True)
338            return None, None, None
339
340        # Extract all frames from the BPF buffer
341        self.extract_frames(bpf_buffer)
342        return self.get_frame()
343
344
345class L2bpfSocket(L2bpfListenSocket):
346    """"Scapy L2 BPF Super Socket"""
347
348    def send(self, x):
349        """Send a frame"""
350        return os.write(self.outs, raw(x))
351
352    def nonblock_recv(self):
353        """Non blocking receive"""
354
355        if self.buffered_frames():
356            # Get a frame from the buffer
357            return L2bpfListenSocket.recv(self)
358
359        # Set the non blocking flag, read from the socket, and unset the flag
360        self.set_nonblock(True)
361        pkt = L2bpfListenSocket.recv(self)
362        self.set_nonblock(False)
363        return pkt
364
365
366class L3bpfSocket(L2bpfSocket):
367
368    def recv(self, x=BPF_BUFFER_LENGTH):
369        """Receive on layer 3"""
370        r = SuperSocket.recv(self, x)
371        if r:
372            r.payload.time = r.time
373            return r.payload
374        return r
375
376    def send(self, pkt):
377        """Send a packet"""
378
379        # Use the routing table to find the output interface
380        iff = pkt.route()[0]
381        if iff is None:
382            iff = conf.iface
383
384        # Assign the network interface to the BPF handle
385        if self.assigned_interface != iff:
386            try:
387                fcntl.ioctl(self.outs, BIOCSETIF, struct.pack("16s16x", iff.encode()))  # noqa: E501
388            except IOError:
389                raise Scapy_Exception("BIOCSETIF failed on %s" % iff)
390            self.assigned_interface = iff
391
392        # Build the frame
393        if self.guessed_cls == Loopback:
394            # bpf(4) man page (from macOS, but also for BSD):
395            # "A packet can be sent out on the network by writing to a bpf
396            # file descriptor. [...] Currently only writes to Ethernets and
397            # SLIP links are supported"
398            #
399            # Headers are only mentioned for reads, not writes. tuntaposx's tun
400            # device reports as a "loopback" device, but it does IP.
401            frame = raw(pkt)
402        else:
403            frame = raw(self.guessed_cls() / pkt)
404
405        pkt.sent_time = time.time()
406
407        # Send the frame
408        L2bpfSocket.send(self, frame)
409
410
411# Sockets manipulation functions
412
413def isBPFSocket(obj):
414    """Return True is obj is a BPF Super Socket"""
415    return isinstance(
416        obj,
417        (L2bpfListenSocket, L2bpfListenSocket, L3bpfSocket)
418    )
419
420
421def bpf_select(fds_list, timeout=None):
422    """A call to recv() can return several frames. This functions hides the fact
423       that some frames are read from the internal buffer."""
424
425    # Check file descriptors types
426    bpf_scks_buffered = list()
427    select_fds = list()
428
429    for tmp_fd in fds_list:
430
431        # Specific BPF sockets: get buffers status
432        if isBPFSocket(tmp_fd) and tmp_fd.buffered_frames():
433            bpf_scks_buffered.append(tmp_fd)
434            continue
435
436        # Regular file descriptors or empty BPF buffer
437        select_fds.append(tmp_fd)
438
439    if select_fds:
440        # Call select for sockets with empty buffers
441        if timeout is None:
442            timeout = 0.05
443        ready_list, _, _ = select(select_fds, [], [], timeout)
444        return bpf_scks_buffered + ready_list
445    else:
446        return bpf_scks_buffered
447