# Guillaume Valadon """ Scapy *BSD native support - BPF sockets """ from ctypes import c_long, sizeof import errno import fcntl import os import platform from select import select import struct import time from scapy.arch.bpf.core import get_dev_bpf, attach_filter from scapy.arch.bpf.consts import BIOCGBLEN, BIOCGDLT, BIOCGSTATS, \ BIOCIMMEDIATE, BIOCPROMISC, BIOCSBLEN, BIOCSETIF, BIOCSHDRCMPLT, \ BPF_BUFFER_LENGTH, BIOCSDLT, DLT_IEEE802_11_RADIO from scapy.config import conf from scapy.consts import FREEBSD, NETBSD, DARWIN from scapy.data import ETH_P_ALL from scapy.error import Scapy_Exception, warning from scapy.supersocket import SuperSocket from scapy.compat import raw if FREEBSD: # On 32bit architectures long might be 32bit. BPF_ALIGNMENT = sizeof(c_long) elif NETBSD: BPF_ALIGNMENT = 8 # sizeof(long) else: BPF_ALIGNMENT = 4 # sizeof(int32_t) # SuperSockets definitions class _L2bpfSocket(SuperSocket): """"Generic Scapy BPF Super Socket""" desc = "read/write packets using BPF" nonblocking_socket = True def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None, nofilter=0, monitor=False): self.fd_flags = None self.assigned_interface = None # SuperSocket mandatory variables if promisc is None: self.promisc = conf.sniff_promisc else: self.promisc = promisc if iface is None: self.iface = conf.iface else: self.iface = iface # Get the BPF handle (self.ins, self.dev_bpf) = get_dev_bpf() self.outs = self.ins # Set the BPF buffer length try: fcntl.ioctl(self.ins, BIOCSBLEN, struct.pack('I', BPF_BUFFER_LENGTH)) # noqa: E501 except IOError: raise Scapy_Exception("BIOCSBLEN failed on /dev/bpf%i" % self.dev_bpf) # Assign the network interface to the BPF handle try: fcntl.ioctl(self.ins, BIOCSETIF, struct.pack("16s16x", self.iface.encode())) # noqa: E501 except IOError: raise Scapy_Exception("BIOCSETIF failed on %s" % self.iface) self.assigned_interface = self.iface # Set the interface into promiscuous if self.promisc: self.set_promisc(1) # Set the interface to monitor mode # Note: - trick from libpcap/pcap-bpf.c - monitor_mode() # - it only works on OS X 10.5 and later if DARWIN and monitor: dlt_radiotap = struct.pack('I', DLT_IEEE802_11_RADIO) try: fcntl.ioctl(self.ins, BIOCSDLT, dlt_radiotap) except IOError: raise Scapy_Exception("Can't set %s into monitor mode!" % self.iface) # Don't block on read try: fcntl.ioctl(self.ins, BIOCIMMEDIATE, struct.pack('I', 1)) except IOError: raise Scapy_Exception("BIOCIMMEDIATE failed on /dev/bpf%i" % self.dev_bpf) # Scapy will provide the link layer source address # Otherwise, it is written by the kernel try: fcntl.ioctl(self.ins, BIOCSHDRCMPLT, struct.pack('i', 1)) except IOError: raise Scapy_Exception("BIOCSHDRCMPLT failed on /dev/bpf%i" % self.dev_bpf) # Configure the BPF filter if not nofilter: if conf.except_filter: if filter: filter = "(%s) and not (%s)" % (filter, conf.except_filter) else: filter = "not (%s)" % conf.except_filter if filter is not None: try: attach_filter(self.ins, filter, self.iface) except ImportError as ex: warning("Cannot set filter: %s" % ex) # Set the guessed packet class self.guessed_cls = self.guess_cls() def set_promisc(self, value): """Set the interface in promiscuous mode""" try: fcntl.ioctl(self.ins, BIOCPROMISC, struct.pack('i', value)) except IOError: raise Scapy_Exception("Cannot set promiscuous mode on interface " "(%s)!" % self.iface) def __del__(self): """Close the file descriptor on delete""" # When the socket is deleted on Scapy exits, __del__ is # sometimes called "too late", and self is None if self is not None: self.close() def guess_cls(self): """Guess the packet class that must be used on the interface""" # Get the data link type try: ret = fcntl.ioctl(self.ins, BIOCGDLT, struct.pack('I', 0)) ret = struct.unpack('I', ret)[0] except IOError: cls = conf.default_l2 warning("BIOCGDLT failed: unable to guess type. Using %s !", cls.name) return cls # Retrieve the corresponding class try: return conf.l2types[ret] except KeyError: cls = conf.default_l2 warning("Unable to guess type (type %i). Using %s", ret, cls.name) def set_nonblock(self, set_flag=True): """Set the non blocking flag on the socket""" # Get the current flags if self.fd_flags is None: try: self.fd_flags = fcntl.fcntl(self.ins, fcntl.F_GETFL) except IOError: warning("Cannot get flags on this file descriptor !") return # Set the non blocking flag if set_flag: new_fd_flags = self.fd_flags | os.O_NONBLOCK else: new_fd_flags = self.fd_flags & ~os.O_NONBLOCK try: fcntl.fcntl(self.ins, fcntl.F_SETFL, new_fd_flags) self.fd_flags = new_fd_flags except Exception: warning("Can't set flags on this file descriptor !") def get_stats(self): """Get received / dropped statistics""" try: ret = fcntl.ioctl(self.ins, BIOCGSTATS, struct.pack("2I", 0, 0)) return struct.unpack("2I", ret) except IOError: warning("Unable to get stats from BPF !") return (None, None) def get_blen(self): """Get the BPF buffer length""" try: ret = fcntl.ioctl(self.ins, BIOCGBLEN, struct.pack("I", 0)) return struct.unpack("I", ret)[0] except IOError: warning("Unable to get the BPF buffer length") return def fileno(self): """Get the underlying file descriptor""" return self.ins def close(self): """Close the Super Socket""" if not self.closed and self.ins is not None: os.close(self.ins) self.closed = True self.ins = None def send(self, x): """Dummy send method""" raise Exception( "Can't send anything with %s" % self.__class__.__name__ ) def recv_raw(self, x=BPF_BUFFER_LENGTH): """Dummy recv method""" raise Exception( "Can't recv anything with %s" % self.__class__.__name__ ) @staticmethod def select(sockets, remain=None): """This function is called during sendrecv() routine to select the available sockets. """ # sockets, None (means use the socket's recv() ) return bpf_select(sockets, remain), None class L2bpfListenSocket(_L2bpfSocket): """"Scapy L2 BPF Listen Super Socket""" def __init__(self, *args, **kwargs): self.received_frames = [] super(L2bpfListenSocket, self).__init__(*args, **kwargs) def buffered_frames(self): """Return the number of frames in the buffer""" return len(self.received_frames) def get_frame(self): """Get a frame or packet from the received list""" if self.received_frames: return self.received_frames.pop(0) else: return None, None, None @staticmethod def bpf_align(bh_h, bh_c): """Return the index to the end of the current packet""" # from return ((bh_h + bh_c) + (BPF_ALIGNMENT - 1)) & ~(BPF_ALIGNMENT - 1) def extract_frames(self, bpf_buffer): """Extract all frames from the buffer and stored them in the received list.""" # noqa: E501 # Ensure that the BPF buffer contains at least the header len_bb = len(bpf_buffer) if len_bb < 20: # Note: 20 == sizeof(struct bfp_hdr) return # Extract useful information from the BPF header if FREEBSD: # Unless we set BIOCSTSTAMP to something different than # BPF_T_MICROTIME, we will get bpf_hdr on FreeBSD, which means # that we'll get a struct timeval, which is time_t, suseconds_t. # On i386 time_t is 32bit so the bh_tstamp will only be 8 bytes. # We really want to set BIOCSTSTAMP to BPF_T_NANOTIME and be # done with this and it always be 16? if platform.machine() == "i386": # struct bpf_hdr bh_tstamp_offset = 8 else: # struct bpf_hdr (64bit time_t) or struct bpf_xhdr bh_tstamp_offset = 16 elif NETBSD: # struct bpf_hdr or struct bpf_hdr32 bh_tstamp_offset = 16 else: # struct bpf_hdr bh_tstamp_offset = 8 # Parse the BPF header bh_caplen = struct.unpack('I', bpf_buffer[bh_tstamp_offset:bh_tstamp_offset + 4])[0] # noqa: E501 next_offset = bh_tstamp_offset + 4 bh_datalen = struct.unpack('I', bpf_buffer[next_offset:next_offset + 4])[0] # noqa: E501 next_offset += 4 bh_hdrlen = struct.unpack('H', bpf_buffer[next_offset:next_offset + 2])[0] # noqa: E501 if bh_datalen == 0: return # Get and store the Scapy object frame_str = bpf_buffer[bh_hdrlen:bh_hdrlen + bh_caplen] self.received_frames.append( (self.guessed_cls, frame_str, None) ) # Extract the next frame end = self.bpf_align(bh_hdrlen, bh_caplen) if (len_bb - end) >= 20: self.extract_frames(bpf_buffer[end:]) def recv_raw(self, x=BPF_BUFFER_LENGTH): """Receive a frame from the network""" x = min(x, BPF_BUFFER_LENGTH) if self.buffered_frames(): # Get a frame from the buffer return self.get_frame() # Get data from BPF try: bpf_buffer = os.read(self.ins, x) except EnvironmentError as exc: if exc.errno != errno.EAGAIN: warning("BPF recv_raw()", exc_info=True) return None, None, None # Extract all frames from the BPF buffer self.extract_frames(bpf_buffer) return self.get_frame() class L2bpfSocket(L2bpfListenSocket): """"Scapy L2 BPF Super Socket""" def send(self, x): """Send a frame""" return os.write(self.outs, raw(x)) def nonblock_recv(self): """Non blocking receive""" if self.buffered_frames(): # Get a frame from the buffer return L2bpfListenSocket.recv(self) # Set the non blocking flag, read from the socket, and unset the flag self.set_nonblock(True) pkt = L2bpfListenSocket.recv(self) self.set_nonblock(False) return pkt class L3bpfSocket(L2bpfSocket): def recv(self, x=BPF_BUFFER_LENGTH): """Receive on layer 3""" r = SuperSocket.recv(self, x) if r: r.payload.time = r.time return r.payload return r def send(self, pkt): """Send a packet""" # Use the routing table to find the output interface iff = pkt.route()[0] if iff is None: iff = conf.iface # Assign the network interface to the BPF handle if self.assigned_interface != iff: try: fcntl.ioctl(self.outs, BIOCSETIF, struct.pack("16s16x", iff.encode())) # noqa: E501 except IOError: raise Scapy_Exception("BIOCSETIF failed on %s" % iff) self.assigned_interface = iff # Build the frame frame = raw(self.guessed_cls() / pkt) pkt.sent_time = time.time() # Send the frame L2bpfSocket.send(self, frame) # Sockets manipulation functions def isBPFSocket(obj): """Return True is obj is a BPF Super Socket""" return isinstance( obj, (L2bpfListenSocket, L2bpfListenSocket, L3bpfSocket) ) def bpf_select(fds_list, timeout=None): """A call to recv() can return several frames. This functions hides the fact that some frames are read from the internal buffer.""" # Check file descriptors types bpf_scks_buffered = list() select_fds = list() for tmp_fd in fds_list: # Specific BPF sockets: get buffers status if isBPFSocket(tmp_fd) and tmp_fd.buffered_frames(): bpf_scks_buffered.append(tmp_fd) continue # Regular file descriptors or empty BPF buffer select_fds.append(tmp_fd) if select_fds: # Call select for sockets with empty buffers if timeout is None: timeout = 0.05 ready_list, _, _ = select(select_fds, [], [], timeout) return bpf_scks_buffered + ready_list else: return bpf_scks_buffered