# SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Native Microsoft Windows sockets (L3 only) This uses Raw Sockets from winsock https://learn.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2 .. note:: Don't use this module. It is a proof of concept, and a worse-case-scenario failover, but you should consider that raw sockets on Windows don't work and install Npcap to avoid using it at all cost. """ import io import socket import struct import time from scapy.automaton import select_objects from scapy.compat import raw from scapy.config import conf from scapy.data import MTU from scapy.error import Scapy_Exception, log_runtime from scapy.packet import Packet from scapy.interfaces import resolve_iface, _GlobInterfaceType from scapy.supersocket import SuperSocket # Typing imports from typing import ( Any, List, Optional, Tuple, Type, ) # Watch out for import loops (inet...) class L3WinSocket(SuperSocket): """ A L3 raw socket implementation native to Windows. Official "Windows Limitations" from MSDN: - TCP data cannot be sent over raw sockets. - UDP datagrams with an invalid source address cannot be sent over raw sockets. - For IPv6 (address family of AF_INET6), an application receives everything after the last IPv6 header in each received datagram [...]. The application does not receive any IPv6 headers using a raw socket. Unofficial limitations: - Turns out we actually don't see any incoming TCP data, only the outgoing. We do properly see UDP, ICMP, etc. both ways though. - To match IPv6 responses, one must use `conf.checkIPaddr = False` as we can't get the real destination. **To overcome those limitations, install Npcap.** """ desc = "a native Layer 3 (IPv4) raw socket under Windows" nonblocking_socket = True __selectable_force_select__ = True # see automaton.py __slots__ = ["promisc", "cls", "ipv6"] def __init__(self, iface=None, # type: Optional[_GlobInterfaceType] ttl=128, # type: int ipv6=False, # type: bool promisc=True, # type: bool **kwargs # type: Any ): # type: (...) -> None from scapy.layers.inet import IP from scapy.layers.inet6 import IPv6 for kwarg in kwargs: log_runtime.warning("Dropping unsupported option: %s" % kwarg) self.iface = iface and resolve_iface(iface) or conf.iface if not self.iface.is_valid(): log_runtime.warning("Interface is invalid. This will fail.") af = socket.AF_INET6 if ipv6 else socket.AF_INET self.ipv6 = ipv6 self.cls = IPv6 if ipv6 else IP # Promisc if promisc is None: promisc = conf.sniff_promisc self.promisc = promisc # Notes: # - IPPROTO_RAW is broken. We don't use it. # - IPPROTO_IPV6 exists in MSDN docs, but using it will result in # no packets being received. Same for its options (IPV6_HDRINCL...) # However, using IPPROTO_IP with AF_INET6 will still receive # the IPv6 packets try: # Listening on AF_INET6 IPPROTO_IPV6 is broken. Use IPPROTO_IP self.outs = self.ins = socket.socket( af, socket.SOCK_RAW, socket.IPPROTO_IP, ) except OSError as e: if e.errno == 13: raise OSError("Windows native L3 Raw sockets are only " "usable as administrator ! " "Please install Npcap to workaround !") raise self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30) self.outs.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2**30) # set TTL self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl) # Get as much data as possible: reduce what is cropped if ipv6: # IPV6_HDRINCL is broken. Use IP_HDRINCL even on IPv6 self.outs.setsockopt(socket.IPPROTO_IPV6, socket.IP_HDRINCL, 1) try: # Not all Windows versions self.ins.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVTCLASS, 1) self.ins.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_HOPLIMIT, 1) except (OSError, socket.error): pass else: # IOCTL Include IP headers self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) try: # Not Windows XP self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_RECVDSTADDR, 1) except (OSError, socket.error): pass try: # Windows 10+ recent builds only self.ins.setsockopt( socket.IPPROTO_IP, socket.IP_RECVTTL, # type: ignore 1 ) except (OSError, socket.error): pass # Bind on all ports if ipv6: from scapy.arch import get_if_addr6 host = get_if_addr6(self.iface) else: from scapy.arch import get_if_addr host = get_if_addr(self.iface) self.ins.bind((host or socket.gethostname(), 0)) # self.ins.setblocking(False) # Set promisc if promisc: # IOCTL Receive all packets self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) def send(self, x): # type: (Packet) -> int data = raw(x) if self.cls not in x: raise Scapy_Exception("L3WinSocket can only send IP/IPv6 packets !" " Install Npcap/Winpcap to send more") from scapy.layers.inet import TCP if TCP in x: raise Scapy_Exception( "'TCP data cannot be sent over raw socket': " "https://learn.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2" # noqa: E501 ) if not self.outs: raise Scapy_Exception("Socket not created") dst_ip = str(x[self.cls].dst) return self.outs.sendto(data, (dst_ip, 0)) def nonblock_recv(self, x=MTU): # type: (int) -> Optional[Packet] try: return self.recv() except IOError: return None # https://docs.microsoft.com/en-us/windows/desktop/winsock/tcp-ip-raw-sockets-2 # noqa: E501 # - For IPv4 (address family of AF_INET), an application receives the IP # header at the front of each received datagram regardless of the # IP_HDRINCL socket option. # - For IPv6 (address family of AF_INET6), an application receives # everything after the last IPv6 header in each received datagram # regardless of the IPV6_HDRINCL socket option. The application does # not receive any IPv6 headers using a raw socket. def recv_raw(self, x=MTU): # type: (int) -> Tuple[Type[Packet], bytes, float] try: data, address = self.ins.recvfrom(x) except io.BlockingIOError: return None, None, None # type: ignore if self.ipv6: from scapy.layers.inet6 import IPv6 # AF_INET6 does not return the IPv6 header. Let's build it # (host, port, flowinfo, scopeid) host, _, flowinfo, _ = address # We have to guess what the proto is. Ugly heuristics ahead :( # Waiting for https://github.com/python/cpython/issues/80398 if len(data) > 6 and struct.unpack("!H", data[4:6])[0] == len(data): proto = socket.IPPROTO_UDP elif data and data[0] in range(128, 138): # ugh proto = socket.IPPROTO_ICMPV6 else: proto = socket.IPPROTO_TCP header = raw( IPv6( src=host, dst="::", fl=flowinfo, nh=proto or 0xFF, plen=len(data) ) ) return IPv6, header + data, time.time() else: from scapy.layers.inet import IP return IP, data, time.time() def close(self): # type: () -> None if not self.closed and self.promisc and hasattr(self, 'ins'): self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) super(L3WinSocket, self).close() @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] return select_objects(sockets, remain) class L3WinSocket6(L3WinSocket): desc = "a native Layer 3 (IPv6) raw socket under Windows" def __init__(self, **kwargs): # type: (**Any) -> None super(L3WinSocket6, self).__init__( ipv6=True, **kwargs, )