diff --git a/.travis.yml b/.travis.yml index 7ce3255817f177edb95ac6f233f236dd73599811..839733d2f63663b7de744f345ad9bb3b241bbecb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,9 @@ matrix: - os: linux python: pypy + - os: osx + language: generic + - os: osx language: generic env: @@ -25,21 +28,23 @@ matrix: sudo: required python: 2.7 env: - - TRAVIS_SUDO=sudo - - SCAPY_USE_PCAPDNET=yes + - TRAVIS_SUDO=sudo TEST_COMBINED_MODES=yes - os: linux sudo: required python: 2.7 env: - - TRAVIS_SUDO=sudo - - TEST_COMBINED_MODES=yes + - TRAVIS_SUDO=sudo SCAPY_USE_PCAPDNET=yes - os: osx language: generic env: - TRAVIS_SUDO=sudo - - SCAPY_USE_PCAPDNET=yes + + - os: osx + language: generic + env: + - TRAVIS_SUDO=sudo SCAPY_USE_PCAPDNET=yes install: bash .travis/install.sh diff --git a/.travis/test.sh b/.travis/test.sh index 62758444059e6ae91079f956f94c17952725ecf7..ef7e5121ca92a4ec9afe501f0f6f78eb41925756 100644 --- a/.travis/test.sh +++ b/.travis/test.sh @@ -4,22 +4,34 @@ python -c "from scapy.all import *; print conf" # Don't run tests that require root privileges if [ -z "$TRAVIS_SUDO" -o "$TRAVIS_SUDO" = "false" ] then - UT_FLAGS="-K netaccess " + UT_FLAGS="-K netaccess -K needs_root" TRAVIS_SUDO="" fi # Test AEAD modes in IPsec if available if [ "$TEST_COMBINED_MODES" != "yes" ] then - UT_FLAGS+="-K combined_modes " + UT_FLAGS+=" -K combined_modes " fi # Run unit tests cd test/ +if [ "$TRAVIS_OS_NAME" = "osx" ] +then + if [ -z $SCAPY_USE_PCAPDNET ] + then + $TRAVIS_SUDO ./run_tests -q -F -t bpf.uts $UT_FLAGS || exit $? + fi +fi + for f in *.uts do - $TRAVIS_SUDO ./run_tests -f text -t $f $UT_FLAGS || exit $? + if [ "$f" = "bpf.uts" ] + then + continue + fi + $TRAVIS_SUDO ./run_tests -q -F -t $f $UT_FLAGS || exit $? done for f in ../scapy/contrib/*.uts diff --git a/scapy/arch/__init__.py b/scapy/arch/__init__.py index 29e9a69eccc52967dc7dbeaa7e627e4fae22e5a8..581092553e21a612466c24e7413ef82ca6dac441 100644 --- a/scapy/arch/__init__.py +++ b/scapy/arch/__init__.py @@ -44,6 +44,9 @@ def str2mac(s): return ("%02x:"*6)[:-1] % tuple(map(ord, s)) +if not scapy.config.conf.use_pcap and not scapy.config.conf.use_dnet: + from scapy.arch.bpf.core import get_if_raw_addr + def get_if_addr(iff): return socket.inet_ntoa(get_if_raw_addr(iff)) @@ -63,6 +66,7 @@ def get_if_hwaddr(iff): # def attach_filter(s, filter, iface): # def set_promisc(s,iff,val=1): # def read_routes(): +# def read_routes6(): # def get_if(iff,cmd): # def get_if_index(iff): @@ -74,9 +78,16 @@ if LINUX: from scapy.arch.pcapdnet import * elif BSD: from scapy.arch.unix import read_routes, read_routes6, in6_getifaddr - scapy.config.conf.use_pcap = True - scapy.config.conf.use_dnet = True - from scapy.arch.pcapdnet import * + + if scapy.config.conf.use_pcap or scapy.config.conf.use_dnet: + from scapy.arch.pcapdnet import * + else: + from scapy.arch.bpf.supersocket import L2bpfListenSocket, L2bpfSocket, L3bpfSocket + from scapy.arch.bpf.core import * + scapy.config.conf.use_bpf = True + scapy.config.conf.L2listen = L2bpfListenSocket + scapy.config.conf.L2socket = L2bpfSocket + scapy.config.conf.L3socket = L3bpfSocket elif SOLARIS: from scapy.arch.solaris import * elif WINDOWS: diff --git a/scapy/arch/bpf/__init__.py b/scapy/arch/bpf/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c67498a55c5bb4a28741a0357ad994e8f96e746d --- /dev/null +++ b/scapy/arch/bpf/__init__.py @@ -0,0 +1,5 @@ +# Guillaume Valadon <guillaume@valadon.net> + +""" +Scapy *BSD native support +""" diff --git a/scapy/arch/bpf/consts.py b/scapy/arch/bpf/consts.py new file mode 100644 index 0000000000000000000000000000000000000000..d4102d4da40e4341d698c6738a1f7a754c50a200 --- /dev/null +++ b/scapy/arch/bpf/consts.py @@ -0,0 +1,23 @@ +# Guillaume Valadon <guillaume@valadon.net> + +""" +Scapy *BSD native support - constants +""" + + +from scapy.data import MTU + + +SIOCGIFFLAGS = 0xc0206911 +BPF_BUFFER_LENGTH = MTU + +# From net/bpf.h +BIOCIMMEDIATE = 0x80044270 +BIOCGSTATS = 0x4008426f +BIOCPROMISC = 0x20004269 +BIOCSETIF = 0x8020426c +BIOCSBLEN = 0xc0044266 +BIOCGBLEN = 0x40044266 +BIOCSETF = 0x80104267 +BIOCSHDRCMPLT = 0x80044275 +BIOCGDLT = 0x4004426a diff --git a/scapy/arch/bpf/core.py b/scapy/arch/bpf/core.py new file mode 100644 index 0000000000000000000000000000000000000000..99180699383d1aa526b964a230d8ba7ce897dec5 --- /dev/null +++ b/scapy/arch/bpf/core.py @@ -0,0 +1,223 @@ +# Guillaume Valadon <guillaume@valadon.net> + +""" +Scapy *BSD native support - core +""" + +from scapy.config import conf +from scapy.error import Scapy_Exception +from scapy.data import ARPHDR_LOOPBACK, ARPHDR_ETHER +from scapy.arch.common import get_if +from scapy.arch.consts import LOOPBACK_NAME +from scapy.utils import warning + +from scapy.arch.bpf.consts import * + +import os +import socket +import fcntl +import struct + +from ctypes import cdll, cast, pointer, POINTER, Structure +from ctypes import c_uint, c_uint32, c_int, c_ulong, c_char_p, c_ushort, c_ubyte +from ctypes.util import find_library + + +# ctypes definitions + +LIBC = cdll.LoadLibrary(find_library("libc")) +LIBC.ioctl.argtypes = [c_int, c_ulong, c_char_p] +LIBC.ioctl.restype = c_int + + +class bpf_insn(Structure): + """"The BPF instruction data structure""" + _fields_ = [("code", c_ushort), + ("jt", c_ubyte), + ("jf", c_ubyte), + ("k", c_uint32)] + + +class bpf_program(Structure): + """"Structure for BIOCSETF""" + _fields_ = [("bf_len", c_uint), + ("bf_insns", POINTER(bpf_insn))] + + +# Addresses manipulation functions + +def get_if_raw_addr(ifname): + """Returns the IPv4 address configured on 'ifname', packed with inet_pton.""" + + # Get ifconfig output + try: + fd = os.popen("%s %s" % (conf.prog.ifconfig, ifname)) + except OSError, msg: + raise Scapy_Exception("Failed to execute ifconfig: (%s)" % msg) + + # Get IPv4 addresses + addresses = [l for l in fd.readlines() if l.find("netmask") >= 0] + if not addresses: + raise Scapy_Exception("No IPv4 address found on %s !" % ifname) + + # Pack the first address + address = addresses[0].split(' ')[1] + return socket.inet_pton(socket.AF_INET, address) + + +def get_if_raw_hwaddr(ifname): + """Returns the packed MAC address configured on 'ifname'.""" + + NULL_MAC_ADDRESS = '\x00'*6 + + # Handle the loopback interface separately + if ifname == LOOPBACK_NAME: + return (ARPHDR_LOOPBACK, NULL_MAC_ADDRESS) + + # Get ifconfig output + try: + fd = os.popen("%s %s" % (conf.prog.ifconfig, ifname)) + except OSError, msg: + warning("Failed to execute ifconfig: (%s)" % msg) + raise Scapy_Exception("Failed to execute ifconfig: (%s)" % msg) + + # Get MAC addresses + addresses = [l for l in fd.readlines() if l.find("ether") >= 0 or + l.find("lladdr") >= 0 or + l.find("address") >= 0] + if not addresses: + raise Scapy_Exception("No MAC address found on %s !" % ifname) + + # Pack and return the MAC address + mac = addresses[0].split(' ')[1] + mac = [chr(int(b, 16)) for b in mac.split(':')] + return (ARPHDR_ETHER, ''.join(mac)) + + +# BPF specific functions + +def get_dev_bpf(): + """Returns an opened BPF file object""" + + # Get the first available BPF handle + for bpf in range(0, 8): + try: + fd = os.open("/dev/bpf%i" % bpf, os.O_RDWR) + return (fd, bpf) + except OSError, err: + continue + + raise Scapy_Exception("No /dev/bpf handle is available !") + + +def attach_filter(fd, iface, bpf_filter_string): + """Attach a BPF filter to the BPF file descriptor""" + + # Retrieve the BPF byte code in decimal + command = "%s -i %s -ddd -s 1600 '%s'" % (conf.prog.tcpdump, iface, bpf_filter_string) + try: + f = os.popen(command) + except OSError, msg: + raise Scapy_Exception("Failed to execute tcpdump: (%s)" % msg) + + # Convert the byte code to a BPF program structure + lines = f.readlines() + if lines == []: + raise Scapy_Exception("Got an empty BPF filter from tcpdump !") + + # Allocate BPF instructions + size = int(lines[0]) + bpf_insn_a = bpf_insn * size + bip = bpf_insn_a() + + # Fill the BPF instruction structures with the byte code + lines = lines[1:] + for i in xrange(len(lines)): + values = [int(v) for v in lines[i].split()] + bip[i].code = c_ushort(values[0]) + bip[i].jt = c_ubyte(values[1]) + bip[i].jf = c_ubyte(values[2]) + bip[i].k = c_uint(values[3]) + + # Create the BPF program and assign it to the interface + bp = bpf_program(size, bip) + ret = LIBC.ioctl(c_int(fd), BIOCSETF, cast(pointer(bp), c_char_p)) + if ret < 0: + raise Scapy_Exception("Can't attach the BPF filter !") + + +# Interface manipulation functions + +def get_if_list(): + """Returns a list containing all network interfaces.""" + + # Get ifconfig output + try: + fd = os.popen("%s -a" % conf.prog.ifconfig) + except OSError, msg: + raise Scapy_Exception("Failed to execute ifconfig: (%s)" % msg) + + # Get interfaces + interfaces = [line[:line.find(':')] for line in fd.readlines() + if ": flags" in line.lower()] + return interfaces + + +def get_working_ifaces(): + """ + Returns an ordered list of interfaces that could be used with BPF. + Note: the order mimics pcap_findalldevs() behavior + """ + + # Only root is allowed to perform the following ioctl() call + if os.getuid() != 0: + return [] + + # Test all network interfaces + interfaces = [] + for ifname in get_if_list(): + + # Unlike pcap_findalldevs(), we do not care of loopback interfaces. + if ifname == LOOPBACK_NAME: + continue + + # Get interface flags + try: + result = get_if(ifname, SIOCGIFFLAGS) + except IOError, msg: + warning("ioctl(SIOCGIFFLAGS) failed on %s !" % ifname) + continue + + # Convert flags + ifflags = struct.unpack("16xH14x", result)[0] + if ifflags & 0x1: # IFF_UP + + # Get a BPF handle + fd, _ = get_dev_bpf() + if fd is None: + raise Scapy_Exception("No /dev/bpf are available !") + + # Check if the interface can be used + try: + fcntl.ioctl(fd, BIOCSETIF, struct.pack("16s16x", ifname)) + interfaces.append((ifname, int(ifname[-1]))) + except IOError, err: + pass + + # Close the file descriptor + os.close(fd) + + # Sort to mimic pcap_findalldevs() order + interfaces.sort(lambda (ifname_left, ifid_left), + (ifname_right, ifid_right): ifid_left-ifid_right) + return interfaces + + +def get_working_if(): + """Returns the first interface than can be used with BPF""" + + ifaces = get_working_ifaces() + if not ifaces: + # A better interface will be selected later using the routing table + return LOOPBACK_NAME + return ifaces[0][0] diff --git a/scapy/arch/bpf/supersocket.py b/scapy/arch/bpf/supersocket.py new file mode 100644 index 0000000000000000000000000000000000000000..a74d8f82e772f9143a929e1ec0efa97518893ec0 --- /dev/null +++ b/scapy/arch/bpf/supersocket.py @@ -0,0 +1,385 @@ +# Guillaume Valadon <guillaume@valadon.net> + +""" +Scapy *BSD native support - BPF sockets +""" + +from scapy.config import conf +from scapy.error import Scapy_Exception +from scapy.supersocket import SuperSocket +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP +from scapy.layers.inet6 import IPv6 +from scapy.packet import Raw +from scapy.data import ETH_P_ALL +from scapy.arch.consts import FREEBSD, OPENBSD, NETBSD +from scapy.utils import warning + +from scapy.arch.bpf.core import get_dev_bpf, attach_filter +from scapy.arch.bpf.consts import * + +import struct +import fcntl +import os +import time +import errno +from select import select + + +# 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, err: + msg = "BIOCSBLEN failed on /dev/bpf%i" % self.dev_bpf + raise Scapy_Exception(msg) + + # Assign the network interface to the BPF handle + try: + fcntl.ioctl(self.ins, BIOCSETIF, struct.pack("16s16x", self.iface)) + except IOError, err: + msg = "BIOCSETIF failed on %s" % self.iface + raise Scapy_Exception(msg) + 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, err: + msg = "BIOCIMMEDIATE failed on /dev/bpf%i" % self.dev_bpf + raise Scapy_Exception(msg) + + # 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, err: + msg = "BIOCSHDRCMPLT failed on /dev/bpf%i" % self.dev_bpf + raise Scapy_Exception(msg) + + # 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, err: + msg = "Can't put your interface (%s) into promiscuous mode !" % self.iface + raise Scapy_Exception(msg) + + def __del__(self): + """Close the file descriptor on delete""" + 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, err: + warning("BIOCGDLT failed: unable to guess type. Using Ethernet !") + return Ether + + # *BSD loopback interface + if OPENBSD and ret == 12: # DTL_NULL on OpenBSD + return Loopback + + # Retrieve the corresponding class + cls = conf.l2types.get(ret, None) + if cls is None: + cls = Ether + warning("Unable to guess type. Using Ethernet !") + + return cls + + 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, err: + warning("Can't 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, err: + 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, err: + 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: + pkt = self.received_frames.pop(0) + if isinstance(self, L3bpfSocket): + pkt = pkt.payload + return pkt + + return None + + def bpf_align(self, bh_h, bh_c): + """Return the index to the end of the current packet""" + + if FREEBSD or NETBSD: + BPF_ALIGNMENT = 8 # sizeof(long) + else: + BPF_ALIGNMENT = 4 # sizeof(int32_t) + + x = bh_h + bh_c + return ((x)+(BPF_ALIGNMENT-1)) & ~(BPF_ALIGNMENT-1) # from <net/bpf.h> + + 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 = Raw(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() + + else: + # Get data from BPF + try: + bpf_buffer = os.read(self.ins, x) + except EnvironmentError, e: + if e.errno == errno.EAGAIN: + return + else: + warning("BPF recv(): %s" % e) + 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, str(x)) + + def nonblock_recv(self): + """Non blocking receive""" + + if self.buffered_frames(): + # Get a frame from the buffer + return self.get_frame() + + else: + # 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 send(self, pkt): + """Send a packet""" + + # Use the routing table to find the output interface + if isinstance(pkt, IPv6): + iff, a, gw = conf.route6.route(pkt.dst) + if isinstance(pkt, IP): + iff, a, gw = conf.route.route(pkt.dst) + else: + 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)) + except IOError, err: + msg = "BIOCSETIF failed on %s" % iff + raise Scapy_Exception(msg) + self.assigned_interface = iff + + # Build the frame + frame = str(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 + if isBPFSocket(tmp_fd): + # Get buffers status + if tmp_fd.buffered_frames(): + bpf_scks_buffered.append(tmp_fd) + continue + + # Regular file descriptors or empty BPF buffer + select_fds.append(tmp_fd) + + if len(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 diff --git a/scapy/arch/unix.py b/scapy/arch/unix.py index 66400ad97985af7d1497a1b34a1ca5434bb92435..0e3428cbd0fcabac0ba53d2acf4c177274c0fc7d 100644 --- a/scapy/arch/unix.py +++ b/scapy/arch/unix.py @@ -16,7 +16,8 @@ import scapy.config import scapy.utils from scapy.utils6 import in6_getscope, construct_source_candidate_set from scapy.utils6 import in6_isvalid, in6_ismlladdr, in6_ismnladdr -import scapy.arch +from scapy.arch.consts import FREEBSD, NETBSD, OPENBSD, SOLARIS, LOOPBACK_NAME +from scapy.arch import get_if_addr from scapy.config import conf @@ -41,9 +42,9 @@ def _guess_iface_name(netif): def read_routes(): - if scapy.arch.SOLARIS: + if SOLARIS: f=os.popen("netstat -rvn") # -f inet - elif scapy.arch.FREEBSD: + elif FREEBSD: f=os.popen("netstat -rnW") # -W to handle long interface names else: f=os.popen("netstat -rn") # -f inet @@ -67,7 +68,7 @@ def read_routes(): continue if not l: break - if scapy.arch.SOLARIS: + if SOLARIS: lspl = l.split() if len(lspl) == 10: dest,mask,gw,netif,mxfrg,rtt,ref,flg = lspl[:8] @@ -84,7 +85,7 @@ def read_routes(): dest = 0L netmask = 0L else: - if scapy.arch.SOLARIS: + if SOLARIS: netmask = scapy.utils.atol(mask) elif "/" in dest: dest,netmask = dest.split("/") @@ -97,7 +98,7 @@ def read_routes(): gw = '0.0.0.0' if netif is not None: try: - ifaddr = scapy.arch.get_if_addr(netif) + ifaddr = get_if_addr(netif) routes.append((dest,netmask,gw,netif,ifaddr)) except OSError as exc: if exc.message == 'Device not configured': @@ -106,7 +107,7 @@ def read_routes(): # ignore it. guessed_netif = _guess_iface_name(netif) if guessed_netif is not None: - ifaddr = scapy.arch.get_if_addr(guessed_netif) + ifaddr = get_if_addr(guessed_netif) routes.append((dest, netmask, gw, guessed_netif, ifaddr)) else: warning("Could not guess partial interface name: %s" % netif) @@ -184,7 +185,7 @@ def in6_getifaddr(): """ # List all network interfaces - if scapy.arch.OPENBSD: + if OPENBSD: try: f = os.popen("%s" % conf.prog.ifconfig) except OSError,msg: @@ -244,7 +245,7 @@ def read_routes6(): # Parse a route entry according to the operating system splitted_line = line.split() - if scapy.arch.OPENBSD or scapy.arch.NETBSD: + if OPENBSD or NETBSD: index = 5 + mtu_present + prio_present if len(splitted_line) < index: warning("Not enough columns in route entry !") @@ -312,14 +313,14 @@ def read_routes6(): # Note: multicast routing is handled in Route6.route() continue - if scapy.arch.LOOPBACK_NAME in dev: + if LOOPBACK_NAME in dev: # Handle ::1 separately cset = ["::1"] next_hop = "::" else: # Get possible IPv6 source addresses devaddrs = filter(lambda x: x[2] == dev, lifaddr) - cset = construct_source_candidate_set(destination, destination_plen, devaddrs, scapy.arch.LOOPBACK_NAME) + cset = construct_source_candidate_set(destination, destination_plen, devaddrs, LOOPBACK_NAME) if len(cset): routes.append((destination, destination_plen, next_hop, dev, cset)) diff --git a/scapy/config.py b/scapy/config.py index d66a6443a843b4075986ac2802a06ce727e78425..032ab871593b68fb75b8ec7a8441f18a08cee7e7 100755 --- a/scapy/config.py +++ b/scapy/config.py @@ -367,6 +367,7 @@ contribs: a dict which can be used by contrib layers to store local configuratio emph = Emphasize() use_pcap = os.getenv("SCAPY_USE_PCAPDNET", "").lower().startswith("y") use_dnet = os.getenv("SCAPY_USE_PCAPDNET", "").lower().startswith("y") + use_bpf = False use_winpcapy = False ipv6_enabled = socket.has_ipv6 ethertypes = ETHER_TYPES diff --git a/scapy/data.py b/scapy/data.py index 890b00af731584df723823f7204c031948edf29b..5a46311957afe11df02e8d0d090308766e32c0dd 100644 --- a/scapy/data.py +++ b/scapy/data.py @@ -30,6 +30,8 @@ ARPHDR_PPP = 512 ARPHDR_LOOPBACK = 772 ARPHDR_TUN = 65534 +# From net/bpf.h +DLT_NULL = 0 # From net/ipv6.h on Linux (+ Additions) IPV6_ADDR_UNICAST = 0x01 diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py index c83fff54538d05cbfab3cc4598dd1c8827a8ffab..00f78e3079b6c1711ce6e6767f89f817c9bac715 100644 --- a/scapy/layers/inet.py +++ b/scapy/layers/inet.py @@ -761,6 +761,7 @@ bind_layers( Ether, IP, type=2048) bind_layers( CookedLinux, IP, proto=2048) bind_layers( GRE, IP, proto=2048) bind_layers( SNAP, IP, code=2048) +bind_layers( Loopback, IP, type=2) bind_layers( IPerror, IPerror, frag=0, proto=4) bind_layers( IPerror, ICMPerror, frag=0, proto=1) bind_layers( IPerror, TCPerror, frag=0, proto=6) diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py index 124d913e75828aea3849266130bf05a56d039beb..86c63aea50b25b421b12fae2603a4f5a2576824b 100644 --- a/scapy/layers/inet6.py +++ b/scapy/layers/inet6.py @@ -3724,6 +3724,7 @@ conf.l2types.register(31, IPv6) bind_layers(Ether, IPv6, type = 0x86dd ) bind_layers(CookedLinux, IPv6, proto = 0x86dd ) +bind_layers(Loopback, IPv6, type = 0x1c ) bind_layers(IPerror6, TCPerror, nh = socket.IPPROTO_TCP ) bind_layers(IPerror6, UDPerror, nh = socket.IPPROTO_UDP ) bind_layers(IPv6, TCP, nh = socket.IPPROTO_TCP ) diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py index 4f491d299b7a7184ddfaae39d51b312f358f195d..c13bbc681a23a24f7bf84ae79c3d795159156dc9 100644 --- a/scapy/layers/l2.py +++ b/scapy/layers/l2.py @@ -16,7 +16,7 @@ from scapy.packet import * from scapy.ansmachine import * from scapy.plist import SndRcvList from scapy.fields import * -from scapy.sendrecv import * +from scapy.sendrecv import srp,srp1 from scapy.arch import get_if_hwaddr from scapy.arch.consts import LOOPBACK_NAME from scapy.utils import inet_ntoa, inet_aton @@ -603,10 +603,30 @@ class GRE(Packet): return p +### *BSD loopback layer + +class LoIntEnumField(EnumField): + def __init__(self, name, default, enum): + EnumField.__init__(self, name, default, enum, "!I") + + def m2i(self, pkt, x): + return x >> 24 + + def i2m(self, pkt, x): + return x << 24 + +LOOPBACK_TYPES = { 0x2: "IPv4", 0x1c: "IPv6" } + +class Loopback(Packet): + """*BSD loopback layer""" + + name = "Loopback" + fields_desc = [ LoIntEnumField("type", 0x2, LOOPBACK_TYPES) ] + + class Dot1AD(Dot1Q): name = '802_1AD' - bind_layers( Dot3, LLC, ) bind_layers( Ether, LLC, type=122) bind_layers( Ether, LLC, type=34928) @@ -652,6 +672,7 @@ conf.l2types.register_num2layer(ARPHDR_LOOPBACK, Ether) conf.l2types.register_layer2num(ARPHDR_ETHER, Dot3) conf.l2types.register(144, CookedLinux) # called LINUX_IRDA, similar to CookedLinux conf.l2types.register(113, CookedLinux) +conf.l2types.register(DLT_NULL, Loopback) conf.l3types.register(ETH_P_ARP, ARP) diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py index cf109f33478c3b0c8f9f41578dcc97c13ec9002c..5f6274b7deb46064f227106437a3ddd282adbb5e 100644 --- a/scapy/sendrecv.py +++ b/scapy/sendrecv.py @@ -127,7 +127,12 @@ def sndrcv(pks, pkt, timeout = None, inter = 0, verbose=None, chainCC=0, retry=0 if remaintime <= 0: break r = None - if not isinstance(pks, StreamSocket) and (FREEBSD or DARWIN): + if conf.use_bpf: + from scapy.arch.bpf.supersocket import bpf_select + inp = bpf_select(inmask) + if pks in inp: + r = pks.recv() + elif not isinstance(pks, StreamSocket) and (FREEBSD or DARWIN): inp, out, err = select(inmask,[],[], 0.05) if len(inp) == 0 or pks in inp: r = pks.nonblock_recv() @@ -488,7 +493,13 @@ def sndrcvflood(pks, pkt, prn=lambda (s,r):r.summary(), chainCC=0, store=1, uniq try: while 1: - readyr,readys,_ = select([rsock],[ssock],[]) + if conf.use_bpf: + from scapy.arch.bpf.supersocket import bpf_select + readyr = bpf_select([rsock]) + _, readys, _ = select([], [ssock], []) + else: + readyr, readys, _ = select([rsock], [ssock], []) + if ssock in readys: pks.send(packets_to_send.next()) @@ -605,8 +616,12 @@ interfaces) remain = stoptime-time.time() if remain <= 0: break - sel = select(sniff_sockets, [], [], remain) - for s in sel[0]: + if conf.use_bpf: + from scapy.arch.bpf.supersocket import bpf_select + ins = bpf_select(sniff_sockets, remain) + else: + ins, _, _ = select(sniff_sockets, [], [], remain) + for s in ins: p = s.recv() if p is None and offline is not None: stop_event = True @@ -678,7 +693,12 @@ stop_filter: python function applied to each packet to determine remain = stoptime-time.time() if remain <= 0: break - ins, outs, errs = select([s1, s2], [], [], remain) + if conf.use_bpf: + from scapy.arch.bpf.supersocket import bpf_select + ins = bpf_select([s1, s2], remain) + else: + ins, _, _ = select([s1, s2], [], [], remain) + for s in ins: p = s.recv() if p is not None: diff --git a/test/bpf.uts b/test/bpf.uts new file mode 100644 index 0000000000000000000000000000000000000000..13ca7d27dcdbbe12dc25fb4e30c2d45a79b9554e --- /dev/null +++ b/test/bpf.uts @@ -0,0 +1,144 @@ +% Regression tests for Scapy BPF mode + +# More informations at http://www.secdev.org/projects/UTscapy/ + + +############ +############ ++ Addresses manipulation functions + += Get the packet IPv4 address configured on conf.iface + +get_if_raw_addr(conf.iface) + + += Get the packed MAC address of conf.iface + +get_if_raw_hwaddr(conf.iface) + += Get the packed MAC address of LOOPBACK_NAME + +get_if_raw_hwaddr(LOOPBACK_NAME) == (ARPHDR_LOOPBACK, '\x00'*6) + + +############ +############ ++ BPF related functions + += Get a BPF handler +~ needs_root + +from scapy.arch.bpf.supersocket import get_dev_bpf +fd, _ = get_dev_bpf() + += Attach a BPF filter +~ needs_root + +from scapy.arch.bpf.supersocket import attach_filter +attach_filter(fd, conf.iface, "arp or icmp") + + += Get network interfaces list + +iflist = get_if_list() +len(iflist) > 0 + + += Get working network interfaces +~ needs_root + +from scapy.arch.bpf.core import get_working_ifaces +ifworking = get_working_ifaces() +len(ifworking) + +from scapy.arch.bpf.core import get_working_if +len(ifworking) and get_working_if() == ifworking[0][0] + + += Misc functions +~ needs_root + +from scapy.arch.bpf.supersocket import isBPFSocket, bpf_select +isBPFSocket(L2bpfListenSocket()) and isBPFSocket(L2bpfSocket()) and isBPFSocket(L3bpfSocket()) + +l = bpf_select([L2bpfSocket()]) +l = bpf_select([L2bpfSocket(), sys.stdin.fileno()]) + + +############ +############ ++ BPF sockets + += L2bpfListenSocket - initialization variants +~ needs_root + +L2bpfListenSocket() +L2bpfListenSocket(iface=conf.iface) +L2bpfListenSocket(promisc=True) +L2bpfListenSocket(filter="icmp") +L2bpfListenSocket(iface=conf.iface, promisc=True, filter="icmp") + + += L2bpfListenSocket - set_*() +~ needs_root + +s = L2bpfListenSocket() +s.set_promisc(0) +s.set_nonblock(1) +s.set_promisc(0) +s.close() + +s = L2bpfListenSocket() +s.set_nonblock(set_flag=False) +s.set_nonblock(set_flag=True) +s.set_nonblock(set_flag=False) +s.close() + + += L2bpfListenSocket - get_*() +~ needs_root + +s = L2bpfListenSocket() +blen = s.get_blen() +blen > 0 and type(blen) == int +s.close() + +s = L2bpfListenSocket() +stats = s.get_stats() +len(stats) == 2 and type(stats) == tuple +s.close() + + += L2bpfListenSocket - other methods +~ needs_root + +s = L2bpfListenSocket() +type(s.fileno()) == int +s.close() + +s = L2bpfListenSocket() +guessed = s.guess_cls() +issubclass(guessed, Packet) +s.close() + + += L2bpfSocket - nonblock_recv() +~ needs_root + +s = L2bpfSocket() +s.nonblock_recv() +s.close() + + += L*bpfSocket - send() +~ needs_root + +s = L2bpfSocket() +s.send(Ether()/IP(dst="8.8.8.8")/ICMP()) + +s = L3bpfSocket() +s.send(IP(dst="8.8.8.8")/ICMP()) + +s = L3bpfSocket() +s.assigned_interface = LOOPBACK_NAME +s.send(IP(dst="8.8.8.8")/ICMP()) diff --git a/test/regression.uts b/test/regression.uts index 9bbaae07a73b2900f16ae7795128e6742936f37d..eda698d9b47f348e858d635d037cd2fe57634837 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -2645,6 +2645,11 @@ conf.route6.routes=[ ( '::', 0, 'fe80::20f:34ff:fe8a:8aa1', 'eth0', ['2001:db8:0:4444:20f:1fff:feca:4650', '2002:db8:0:4444:20f:1fff:feca:4650']) ] conf.route6.route("2002::1") == ('eth0', '2002:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') and conf.route6.route("2001::1") == ('eth0', '2001:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') and conf.route6.route("fe80::20f:1fff:feab:4870") == ('eth0', 'fe80::20f:1fff:feca:4650', '::') and conf.route6.route("::1") == ('lo', '::1', '::') and conf.route6.route("::") == ('eth0', '2001:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') +conf.route6.resync() +if not len(conf.route6.routes): + # IPv6 seems disabled. Force a route to ::1 + conf.route6.routes.append(("::1", 128, "::", LOOPBACK_NAME, ["::1"])) + True # There are many other to do. @@ -4561,7 +4566,7 @@ assert isinstance(pkt, DNS) and isinstance(pkt.payload, NoPayload) import mock import StringIO -@mock.patch("scapy.arch.get_if_addr") +@mock.patch("scapy.arch.unix.get_if_addr") @mock.patch("scapy.arch.unix.os") def test_osx_netstat_truncated(mock_os, mock_get_if_addr): """Test read_routes() on OS X 10.? with a long interface name""" @@ -4580,7 +4585,7 @@ default link#11 UCSI 1 0 bridge1 # Mocked file descriptors def se_popen(command): """Perform specific side effects""" - if command == "netstat -rn": + if command.startswith("netstat -rn"): return StringIO.StringIO(netstat_output) elif command == "ifconfig -l": ret = StringIO.StringIO(ifconfig_output) @@ -4807,7 +4812,7 @@ test_freebsd_10_2() = OpenBSD 5.5 -@mock.patch("scapy.arch.OPENBSD") +@mock.patch("scapy.arch.unix.OPENBSD") @mock.patch("scapy.arch.unix.in6_getifaddr") @mock.patch("scapy.arch.unix.os") def test_openbsd_5_5(mock_os, mock_in6_getifaddr, mock_openbsd): @@ -4864,7 +4869,7 @@ test_openbsd_5_5() = NetBSD 7.0 -@mock.patch("scapy.arch.NETBSD") +@mock.patch("scapy.arch.unix.NETBSD") @mock.patch("scapy.arch.unix.in6_getifaddr") @mock.patch("scapy.arch.unix.os") def test_netbsd_7_0(mock_os, mock_in6_getifaddr, mock_netbsd):