Home | History | Annotate | Download | only in bpf
      1 # Guillaume Valadon <guillaume (at] valadon.net>
      2 
      3 """
      4 Scapy *BSD native support - BPF sockets
      5 """
      6 
      7 import errno
      8 import fcntl
      9 import os
     10 from select import select
     11 import struct
     12 import time
     13 
     14 from scapy.arch.bpf.core import get_dev_bpf, attach_filter
     15 from scapy.arch.bpf.consts import BIOCGBLEN, BIOCGDLT, BIOCGSTATS, \
     16     BIOCIMMEDIATE, BIOCPROMISC, BIOCSBLEN, BIOCSETIF, BIOCSHDRCMPLT, \
     17     BPF_BUFFER_LENGTH
     18 from scapy.config import conf
     19 from scapy.consts import FREEBSD, NETBSD
     20 from scapy.data import ETH_P_ALL
     21 from scapy.error import Scapy_Exception, warning
     22 from scapy.supersocket import SuperSocket
     23 from scapy.compat import raw
     24 
     25 
     26 if FREEBSD or NETBSD:
     27     BPF_ALIGNMENT = 8  # sizeof(long)
     28 else:
     29     BPF_ALIGNMENT = 4  # sizeof(int32_t)
     30 
     31 
     32 # SuperSockets definitions
     33 
     34 class _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 
    206 class 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 
    287 class 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 
    308 class 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 
    342 def 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 
    347 def 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