# SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ SuperSocket. """ from select import select, error as select_error import ctypes import errno import socket import struct import time from scapy.config import conf from scapy.consts import DARWIN, WINDOWS from scapy.data import ( MTU, ETH_P_IP, ETH_P_IPV6, SOL_PACKET, SO_TIMESTAMPNS, ) from scapy.compat import raw from scapy.error import warning, log_runtime from scapy.interfaces import network_name from scapy.packet import Packet, NoPayload from scapy.plist import ( PacketList, SndRcvList, _PacketIterable, ) from scapy.utils import PcapReader, tcpdump # Typing imports from scapy.interfaces import _GlobInterfaceType from typing import ( Any, Dict, Iterator, List, Optional, Tuple, Type, cast, ) # Utils class _SuperSocket_metaclass(type): desc = None # type: Optional[str] def __repr__(self): # type: () -> str if self.desc is not None: return "<%s: %s>" % (self.__name__, self.desc) else: return "<%s>" % self.__name__ # Used to get ancillary data PACKET_AUXDATA = 8 ETH_P_8021Q = 0x8100 TP_STATUS_VLAN_VALID = 1 << 4 TP_STATUS_VLAN_TPID_VALID = 1 << 6 class tpacket_auxdata(ctypes.Structure): _fields_ = [ ("tp_status", ctypes.c_uint), ("tp_len", ctypes.c_uint), ("tp_snaplen", ctypes.c_uint), ("tp_mac", ctypes.c_ushort), ("tp_net", ctypes.c_ushort), ("tp_vlan_tci", ctypes.c_ushort), ("tp_vlan_tpid", ctypes.c_ushort), ] # type: List[Tuple[str, Any]] # SuperSocket class SuperSocket(metaclass=_SuperSocket_metaclass): closed = False # type: bool nonblocking_socket = False # type: bool auxdata_available = False # type: bool def __init__(self, family=socket.AF_INET, # type: int type=socket.SOCK_STREAM, # type: int proto=0, # type: int iface=None, # type: Optional[_GlobInterfaceType] **kwargs # type: Any ): # type: (...) -> None self.ins = socket.socket(family, type, proto) # type: socket.socket self.outs = self.ins # type: Optional[socket.socket] self.promisc = conf.sniff_promisc self.iface = iface or conf.iface def send(self, x): # type: (Packet) -> int """Sends a `Packet` object :param x: `Packet` to be send :return: Number of bytes that have been sent """ sx = raw(x) try: x.sent_time = time.time() except AttributeError: pass if self.outs: return self.outs.send(sx) else: return 0 if WINDOWS: def _recv_raw(self, sock, x): # type: (socket.socket, int) -> Tuple[bytes, Any, Optional[float]] """Internal function to receive a Packet. :param sock: Socket object from which data are received :param x: Number of bytes to be received :return: Received bytes, address information and no timestamp """ pkt, sa_ll = sock.recvfrom(x) return pkt, sa_ll, None else: def _recv_raw(self, sock, x): # type: (socket.socket, int) -> Tuple[bytes, Any, Optional[float]] """Internal function to receive a Packet, and process ancillary data. :param sock: Socket object from which data are received :param x: Number of bytes to be received :return: Received bytes, address information and an optional timestamp """ timestamp = None if not self.auxdata_available: pkt, _, _, sa_ll = sock.recvmsg(x) return pkt, sa_ll, timestamp flags_len = socket.CMSG_LEN(4096) pkt, ancdata, flags, sa_ll = sock.recvmsg(x, flags_len) if not pkt: return pkt, sa_ll, timestamp for cmsg_lvl, cmsg_type, cmsg_data in ancdata: # Check available ancillary data if (cmsg_lvl == SOL_PACKET and cmsg_type == PACKET_AUXDATA): # Parse AUXDATA try: auxdata = tpacket_auxdata.from_buffer_copy(cmsg_data) except ValueError: # Note: according to Python documentation, recvmsg() # can return a truncated message. A ValueError # exception likely indicates that Auxiliary # Data is not supported by the Linux kernel. return pkt, sa_ll, timestamp if auxdata.tp_vlan_tci != 0 or \ auxdata.tp_status & TP_STATUS_VLAN_VALID: # Insert VLAN tag tpid = ETH_P_8021Q if auxdata.tp_status & TP_STATUS_VLAN_TPID_VALID: tpid = auxdata.tp_vlan_tpid tag = struct.pack( "!HH", tpid, auxdata.tp_vlan_tci ) pkt = pkt[:12] + tag + pkt[12:] elif cmsg_lvl == socket.SOL_SOCKET and \ cmsg_type == SO_TIMESTAMPNS: length = len(cmsg_data) if length == 16: # __kernel_timespec tmp = struct.unpack("ll", cmsg_data) elif length == 8: # timespec tmp = struct.unpack("ii", cmsg_data) else: log_runtime.warning("Unknown timespec format.. ?!") continue timestamp = tmp[0] + tmp[1] * 1e-9 return pkt, sa_ll, timestamp def recv_raw(self, x=MTU): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """Returns a tuple containing (cls, pkt_data, time) :param x: Maximum number of bytes to be received, defaults to MTU :return: A tuple, consisting of a Packet type, the received data, and a timestamp """ return conf.raw_layer, self.ins.recv(x), None def recv(self, x=MTU, **kwargs): # type: (int, **Any) -> Optional[Packet] """Receive a Packet according to the `basecls` of this socket :param x: Maximum number of bytes to be received, defaults to MTU :return: The received `Packet` object, or None """ cls, val, ts = self.recv_raw(x) if not val or not cls: return None try: pkt = cls(val, **kwargs) # type: Packet except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: from scapy.sendrecv import debug debug.crashed_on = (cls, val) raise pkt = conf.raw_layer(val) if ts: pkt.time = ts return pkt def fileno(self): # type: () -> int return self.ins.fileno() def close(self): # type: () -> None """Gracefully close this socket """ if self.closed: return self.closed = True if getattr(self, "outs", None): if getattr(self, "ins", None) != self.outs: if self.outs and self.outs.fileno() != -1: self.outs.close() if getattr(self, "ins", None): if self.ins.fileno() != -1: self.ins.close() def sr(self, *args, **kargs): # type: (Any, Any) -> Tuple[SndRcvList, PacketList] """Send and Receive multiple packets """ from scapy import sendrecv return sendrecv.sndrcv(self, *args, **kargs) def sr1(self, *args, **kargs): # type: (Any, Any) -> Optional[Packet] """Send one packet and receive one answer """ from scapy import sendrecv # if not explicitly specified by the user, # set threaded to False in sr1 to remove the overhead # for a Thread creation kargs.setdefault("threaded", False) ans = sendrecv.sndrcv(self, *args, **kargs)[0] # type: SndRcvList if len(ans) > 0: pkt = ans[0][1] # type: Packet return pkt else: return None def sniff(self, *args, **kargs): # type: (Any, Any) -> PacketList from scapy import sendrecv return sendrecv.sniff(opened_socket=self, *args, **kargs) def tshark(self, *args, **kargs): # type: (Any, Any) -> None from scapy import sendrecv sendrecv.tshark(opened_socket=self, *args, **kargs) # TODO: use 'scapy.ansmachine.AnsweringMachine' when typed def am(self, cls, # type: Type[Any] *args, # type: Any **kwargs # type: Any ): # type: (...) -> Any """ Creates an AnsweringMachine associated with this socket. :param cls: A subclass of AnsweringMachine to instantiate """ return cls(*args, opened_socket=self, socket=self, **kwargs) @staticmethod def select(sockets, remain=conf.recv_poll_rate): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] """This function is called during sendrecv() routine to select the available sockets. :param sockets: an array of sockets that need to be selected :returns: an array of sockets that were selected and the function to be called next to get the packets (i.g. recv) """ try: inp, _, _ = select(sockets, [], [], remain) except (IOError, select_error) as exc: # select.error has no .errno attribute if not exc.args or exc.args[0] != errno.EINTR: raise return inp def __del__(self): # type: () -> None """Close the socket""" self.close() def __enter__(self): # type: () -> SuperSocket return self def __exit__(self, exc_type, exc_value, traceback): # type: (Optional[Type[BaseException]], Optional[BaseException], Optional[Any]) -> None # noqa: E501 """Close the socket""" self.close() if not WINDOWS: class L3RawSocket(SuperSocket): desc = "Layer 3 using Raw sockets (PF_INET/SOCK_RAW)" def __init__(self, type=ETH_P_IP, # type: int filter=None, # type: Optional[str] iface=None, # type: Optional[_GlobInterfaceType] promisc=None, # type: Optional[bool] nofilter=0 # type: int ): # type: (...) -> None self.outs = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) # noqa: E501 self.outs.setsockopt(socket.SOL_IP, socket.IP_HDRINCL, 1) self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) # noqa: E501 if iface is not None: iface = network_name(iface) self.iface = iface self.ins.bind((iface, type)) else: self.iface = "any" try: # Receive Auxiliary Data (VLAN tags) self.ins.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1) self.ins.setsockopt( socket.SOL_SOCKET, SO_TIMESTAMPNS, 1 ) self.auxdata_available = True except OSError: # Note: Auxiliary Data is only supported since # Linux 2.6.21 msg = "Your Linux Kernel does not support Auxiliary Data!" log_runtime.info(msg) def recv(self, x=MTU, **kwargs): # type: (int, **Any) -> Optional[Packet] data, sa_ll, ts = self._recv_raw(self.ins, x) if sa_ll[2] == socket.PACKET_OUTGOING: return None if sa_ll[3] in conf.l2types: cls = conf.l2types.num2layer[sa_ll[3]] # type: Type[Packet] lvl = 2 elif sa_ll[1] in conf.l3types: cls = conf.l3types.num2layer[sa_ll[1]] lvl = 3 else: cls = conf.default_l2 warning("Unable to guess type (interface=%s protocol=%#x family=%i). Using %s", sa_ll[0], sa_ll[1], sa_ll[3], cls.name) # noqa: E501 lvl = 3 try: pkt = cls(data, **kwargs) except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: raise pkt = conf.raw_layer(data) if lvl == 2: pkt = pkt.payload if pkt is not None: if ts is None: from scapy.arch.linux import get_last_packet_timestamp ts = get_last_packet_timestamp(self.ins) pkt.time = ts return pkt def send(self, x): # type: (Packet) -> int try: sx = raw(x) if self.outs: x.sent_time = time.time() return self.outs.sendto( sx, (x.dst, 0) ) except AttributeError: raise ValueError( "Missing 'dst' attribute in the first layer to be " "sent using a native L3 socket ! (make sure you passed the " "IP layer)" ) except socket.error as msg: log_runtime.error(msg) return 0 class L3RawSocket6(L3RawSocket): def __init__(self, type: int = ETH_P_IPV6, filter: Optional[str] = None, iface: Optional[_GlobInterfaceType] = None, promisc: Optional[bool] = None, nofilter: bool = False) -> None: # NOTE: if fragmentation is needed, it will be done by the kernel (RFC 2292) # noqa: E501 self.outs = socket.socket( socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_RAW ) self.ins = socket.socket( socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type) ) self.iface = cast(_GlobInterfaceType, iface) class SimpleSocket(SuperSocket): desc = "wrapper around a classic socket" __selectable_force_select__ = True def __init__(self, sock, basecls=None): # type: (socket.socket, Optional[Type[Packet]]) -> None self.ins = sock self.outs = sock if basecls is None: basecls = conf.raw_layer self.basecls = basecls def recv_raw(self, x=MTU): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] return self.basecls, self.ins.recv(x), None if WINDOWS: @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] from scapy.automaton import select_objects return select_objects(sockets, remain) class StreamSocket(SimpleSocket): """ Wrap a stream socket into a layer 2 SuperSocket :param sock: the socket to wrap :param basecls: the base class packet to use to dissect the packet """ desc = "transforms a stream socket into a layer 2" def __init__(self, sock, # type: socket.socket basecls=None, # type: Optional[Type[Packet]] ): # type: (...) -> None from scapy.sessions import streamcls self.rcvcls = streamcls(basecls or conf.raw_layer) self.metadata: Dict[str, Any] = {} self.streamsession: Dict[str, Any] = {} self._buf = b"" super(StreamSocket, self).__init__(sock, basecls=basecls) def recv(self, x=None, **kwargs): # type: (Optional[int], Any) -> Optional[Packet] if x is None: x = MTU # Block but in PEEK mode data = self.ins.recv(x, socket.MSG_PEEK) if data == b"": raise EOFError x = len(data) pkt = self.rcvcls(self._buf + data, self.metadata, self.streamsession) if pkt is None: # Incomplete packet. self._buf += self.ins.recv(x) return self.recv(x) self.metadata.clear() # Strip any madding pad = pkt.getlayer(conf.padding_layer) if pad is not None and pad.underlayer is not None: del pad.underlayer.payload while pad is not None and not isinstance(pad, NoPayload): x -= len(pad.load) pad = pad.payload # Only receive the packet length self.ins.recv(x) self._buf = b"" return pkt class StreamSocketPeekless(StreamSocket): desc = "StreamSocket that doesn't use MSG_PEEK" def __init__(self, sock, basecls=None): # type: (socket.socket, Optional[Type[Packet]]) -> None from scapy.sessions import TCPSession self.sess = TCPSession(app=True) super(StreamSocketPeekless, self).__init__(sock, basecls) # 65535, the default value of x is the maximum length of a TLS record def recv(self, x=None, **kwargs): # type: (Optional[int], **Any) -> Optional[Packet] if x is None: x = MTU # Block try: data = self.ins.recv(x) except OSError: raise EOFError try: pkt = self.sess.process(data, cls=self.basecls) # type: ignore except struct.error: # Buffer underflow pkt = None if data == b"" and not pkt: raise EOFError if not pkt: return self.recv(x) return pkt @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] queued = [ x for x in sockets if isinstance(x, StreamSocketPeekless) and x.sess.data ] if queued: return queued # type: ignore return super(StreamSocketPeekless, StreamSocketPeekless).select( sockets, remain=remain, ) # Old name: SSLStreamSocket SSLStreamSocket = StreamSocketPeekless class L2ListenTcpdump(SuperSocket): desc = "read packets at layer 2 using tcpdump" def __init__(self, iface=None, # type: Optional[_GlobInterfaceType] promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] nofilter=False, # type: bool prog=None, # type: Optional[str] quiet=False, # type: bool *arg, # type: Any **karg # type: Any ): # type: (...) -> None self.outs = None args = ['-w', '-', '-s', '65535'] self.iface = "any" if iface is None and (WINDOWS or DARWIN): self.iface = iface = conf.iface if promisc is None: promisc = conf.sniff_promisc if iface is not None: args.extend(['-i', network_name(iface)]) if not promisc: args.append('-p') 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: args.append(filter) self.tcpdump_proc = tcpdump( None, prog=prog, args=args, getproc=True, quiet=quiet) self.reader = PcapReader(self.tcpdump_proc.stdout) self.ins = self.reader # type: ignore def recv(self, x=MTU, **kwargs): # type: (int, **Any) -> Optional[Packet] return self.reader.recv(x, **kwargs) def close(self): # type: () -> None SuperSocket.close(self) self.tcpdump_proc.kill() @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] if (WINDOWS or DARWIN): return sockets return SuperSocket.select(sockets, remain=remain) # More abstract objects class IterSocket(SuperSocket): desc = "wrapper around an iterable" nonblocking_socket = True def __init__(self, obj): # type: (_PacketIterable) -> None if not obj: self.iter = iter([]) # type: Iterator[Packet] elif isinstance(obj, IterSocket): self.iter = obj.iter elif isinstance(obj, SndRcvList): def _iter(obj=cast(SndRcvList, obj)): # type: (SndRcvList) -> Iterator[Packet] for s, r in obj: if s.sent_time: s.time = s.sent_time yield s yield r self.iter = _iter() elif isinstance(obj, (list, PacketList)): if isinstance(obj[0], bytes): self.iter = iter(obj) else: self.iter = (y for x in obj for y in x) else: self.iter = obj.__iter__() @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Any) -> List[SuperSocket] return sockets def recv(self, x=None, **kwargs): # type: (Optional[int], Any) -> Optional[Packet] try: pkt = next(self.iter) return pkt.__class__(bytes(pkt), **kwargs) except StopIteration: raise EOFError def close(self): # type: () -> None pass