• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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