1# SPDX-License-Identifier: GPL-2.0-only 2# This file is part of Scapy 3# See https://scapy.net/ for more information 4# Copyright (C) Gabriel Potter <gabriel[]potter[]fr> 5 6""" 7Native Microsoft Windows sockets (L3 only) 8 9This uses Raw Sockets from winsock 10https://learn.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2 11 12.. note:: 13 14 Don't use this module. 15 It is a proof of concept, and a worse-case-scenario failover, but you should 16 consider that raw sockets on Windows don't work and install Npcap to avoid using 17 it at all cost. 18""" 19 20import io 21import socket 22import struct 23import time 24 25from scapy.automaton import select_objects 26from scapy.compat import raw 27from scapy.config import conf 28from scapy.data import MTU 29from scapy.error import Scapy_Exception, log_runtime 30from scapy.packet import Packet 31from scapy.interfaces import resolve_iface, _GlobInterfaceType 32from scapy.supersocket import SuperSocket 33 34# Typing imports 35from typing import ( 36 Any, 37 List, 38 Optional, 39 Tuple, 40 Type, 41) 42 43# Watch out for import loops (inet...) 44 45 46class L3WinSocket(SuperSocket): 47 """ 48 A L3 raw socket implementation native to Windows. 49 50 Official "Windows Limitations" from MSDN: 51 - TCP data cannot be sent over raw sockets. 52 - UDP datagrams with an invalid source address cannot be sent over raw sockets. 53 - For IPv6 (address family of AF_INET6), an application receives everything 54 after the last IPv6 header in each received datagram [...]. The application 55 does not receive any IPv6 headers using a raw socket. 56 57 Unofficial limitations: 58 - Turns out we actually don't see any incoming TCP data, only the outgoing. 59 We do properly see UDP, ICMP, etc. both ways though. 60 - To match IPv6 responses, one must use `conf.checkIPaddr = False` as we can't 61 get the real destination. 62 63 **To overcome those limitations, install Npcap.** 64 """ 65 desc = "a native Layer 3 (IPv4) raw socket under Windows" 66 nonblocking_socket = True 67 __selectable_force_select__ = True # see automaton.py 68 __slots__ = ["promisc", "cls", "ipv6"] 69 70 def __init__(self, 71 iface=None, # type: Optional[_GlobInterfaceType] 72 ttl=128, # type: int 73 ipv6=False, # type: bool 74 promisc=True, # type: bool 75 **kwargs # type: Any 76 ): 77 # type: (...) -> None 78 from scapy.layers.inet import IP 79 from scapy.layers.inet6 import IPv6 80 for kwarg in kwargs: 81 log_runtime.warning("Dropping unsupported option: %s" % kwarg) 82 self.iface = iface and resolve_iface(iface) or conf.iface 83 if not self.iface.is_valid(): 84 log_runtime.warning("Interface is invalid. This will fail.") 85 af = socket.AF_INET6 if ipv6 else socket.AF_INET 86 self.ipv6 = ipv6 87 self.cls = IPv6 if ipv6 else IP 88 # Promisc 89 if promisc is None: 90 promisc = conf.sniff_promisc 91 self.promisc = promisc 92 # Notes: 93 # - IPPROTO_RAW is broken. We don't use it. 94 # - IPPROTO_IPV6 exists in MSDN docs, but using it will result in 95 # no packets being received. Same for its options (IPV6_HDRINCL...) 96 # However, using IPPROTO_IP with AF_INET6 will still receive 97 # the IPv6 packets 98 try: 99 # Listening on AF_INET6 IPPROTO_IPV6 is broken. Use IPPROTO_IP 100 self.outs = self.ins = socket.socket( 101 af, 102 socket.SOCK_RAW, 103 socket.IPPROTO_IP, 104 ) 105 except OSError as e: 106 if e.errno == 13: 107 raise OSError("Windows native L3 Raw sockets are only " 108 "usable as administrator ! " 109 "Please install Npcap to workaround !") 110 raise 111 self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 112 self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30) 113 self.outs.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2**30) 114 # set TTL 115 self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl) 116 # Get as much data as possible: reduce what is cropped 117 if ipv6: 118 # IPV6_HDRINCL is broken. Use IP_HDRINCL even on IPv6 119 self.outs.setsockopt(socket.IPPROTO_IPV6, socket.IP_HDRINCL, 1) 120 try: # Not all Windows versions 121 self.ins.setsockopt(socket.IPPROTO_IPV6, 122 socket.IPV6_RECVTCLASS, 1) 123 self.ins.setsockopt(socket.IPPROTO_IPV6, 124 socket.IPV6_HOPLIMIT, 1) 125 except (OSError, socket.error): 126 pass 127 else: 128 # IOCTL Include IP headers 129 self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 130 try: # Not Windows XP 131 self.ins.setsockopt(socket.IPPROTO_IP, 132 socket.IP_RECVDSTADDR, 1) 133 except (OSError, socket.error): 134 pass 135 try: # Windows 10+ recent builds only 136 self.ins.setsockopt( 137 socket.IPPROTO_IP, 138 socket.IP_RECVTTL, # type: ignore 139 1 140 ) 141 except (OSError, socket.error): 142 pass 143 # Bind on all ports 144 if ipv6: 145 from scapy.arch import get_if_addr6 146 host = get_if_addr6(self.iface) 147 else: 148 from scapy.arch import get_if_addr 149 host = get_if_addr(self.iface) 150 self.ins.bind((host or socket.gethostname(), 0)) 151 # self.ins.setblocking(False) 152 # Set promisc 153 if promisc: 154 # IOCTL Receive all packets 155 self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 156 157 def send(self, x): 158 # type: (Packet) -> int 159 data = raw(x) 160 if self.cls not in x: 161 raise Scapy_Exception("L3WinSocket can only send IP/IPv6 packets !" 162 " Install Npcap/Winpcap to send more") 163 from scapy.layers.inet import TCP 164 if TCP in x: 165 raise Scapy_Exception( 166 "'TCP data cannot be sent over raw socket': " 167 "https://learn.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2" # noqa: E501 168 ) 169 if not self.outs: 170 raise Scapy_Exception("Socket not created") 171 dst_ip = str(x[self.cls].dst) 172 return self.outs.sendto(data, (dst_ip, 0)) 173 174 def nonblock_recv(self, x=MTU): 175 # type: (int) -> Optional[Packet] 176 try: 177 return self.recv() 178 except IOError: 179 return None 180 181 # https://docs.microsoft.com/en-us/windows/desktop/winsock/tcp-ip-raw-sockets-2 # noqa: E501 182 # - For IPv4 (address family of AF_INET), an application receives the IP 183 # header at the front of each received datagram regardless of the 184 # IP_HDRINCL socket option. 185 # - For IPv6 (address family of AF_INET6), an application receives 186 # everything after the last IPv6 header in each received datagram 187 # regardless of the IPV6_HDRINCL socket option. The application does 188 # not receive any IPv6 headers using a raw socket. 189 190 def recv_raw(self, x=MTU): 191 # type: (int) -> Tuple[Type[Packet], bytes, float] 192 try: 193 data, address = self.ins.recvfrom(x) 194 except io.BlockingIOError: 195 return None, None, None # type: ignore 196 if self.ipv6: 197 from scapy.layers.inet6 import IPv6 198 # AF_INET6 does not return the IPv6 header. Let's build it 199 # (host, port, flowinfo, scopeid) 200 host, _, flowinfo, _ = address 201 # We have to guess what the proto is. Ugly heuristics ahead :( 202 # Waiting for https://github.com/python/cpython/issues/80398 203 if len(data) > 6 and struct.unpack("!H", data[4:6])[0] == len(data): 204 proto = socket.IPPROTO_UDP 205 elif data and data[0] in range(128, 138): # ugh 206 proto = socket.IPPROTO_ICMPV6 207 else: 208 proto = socket.IPPROTO_TCP 209 header = raw( 210 IPv6( 211 src=host, 212 dst="::", 213 fl=flowinfo, 214 nh=proto or 0xFF, 215 plen=len(data) 216 ) 217 ) 218 return IPv6, header + data, time.time() 219 else: 220 from scapy.layers.inet import IP 221 return IP, data, time.time() 222 223 def close(self): 224 # type: () -> None 225 if not self.closed and self.promisc: 226 self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) 227 super(L3WinSocket, self).close() 228 229 @staticmethod 230 def select(sockets, remain=None): 231 # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] 232 return select_objects(sockets, remain) 233 234 235class L3WinSocket6(L3WinSocket): 236 desc = "a native Layer 3 (IPv6) raw socket under Windows" 237 238 def __init__(self, **kwargs): 239 # type: (**Any) -> None 240 super(L3WinSocket6, self).__init__( 241 ipv6=True, 242 **kwargs, 243 ) 244