| # Guillaume Valadon <guillaume@valadon.net> |
| |
| """ |
| Scapy *BSD native support - BPF sockets |
| """ |
| |
| import errno |
| import fcntl |
| import os |
| 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 |
| from scapy.config import conf |
| from scapy.consts import FREEBSD, NETBSD |
| 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 or 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" |
| assigned_interface = None |
| fd_flags = None |
| ins = None |
| closed = False |
| |
| def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None, nofilter=0): |
| |
| # 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)) |
| 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())) |
| 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) |
| |
| # 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: |
| attach_filter(self.ins, self.iface, filter) |
| |
| # 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: |
| 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.__name__) |
| |
| def recv(self, x=BPF_BUFFER_LENGTH): |
| """Dummy recv method""" |
| raise Exception("Can't recv anything with %s" % self.__name__) |
| |
| |
| class L2bpfListenSocket(_L2bpfSocket): |
| """"Scapy L2 BPF Listen Super Socket""" |
| |
| received_frames = [] |
| |
| 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) |
| |
| @staticmethod |
| def bpf_align(bh_h, bh_c): |
| """Return the index to the end of the current packet""" |
| |
| # from <net/bpf.h> |
| 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.""" |
| |
| # 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 or NETBSD: |
| # struct bpf_xhdr 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] |
| next_offset = bh_tstamp_offset + 4 |
| bh_datalen = struct.unpack('I', bpf_buffer[next_offset:next_offset+4])[0] |
| next_offset += 4 |
| bh_hdrlen = struct.unpack('H', bpf_buffer[next_offset:next_offset+2])[0] |
| if bh_datalen == 0: |
| return |
| |
| # Get and store the Scapy object |
| frame_str = bpf_buffer[bh_hdrlen:bh_hdrlen+bh_caplen] |
| try: |
| pkt = self.guessed_cls(frame_str) |
| except: |
| if conf.debug_dissector: |
| raise |
| pkt = conf.raw_layer(frame_str) |
| self.received_frames.append(pkt) |
| |
| # 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(self, x=BPF_BUFFER_LENGTH): |
| """Receive a frame from the network""" |
| |
| 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()", exc_info=True) |
| return |
| |
| # 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 self.get_frame() |
| |
| # 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 get_frame(self): |
| """Get a frame or packet from the received list""" |
| pkt = super(L3bpfSocket, self).get_frame() |
| if pkt is not None: |
| return pkt.payload |
| |
| 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())) |
| 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) or isinstance(obj, L2bpfListenSocket) or isinstance(obj, 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 |