• 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"""
7Customizations needed to support Microsoft Windows.
8"""
9
10from glob import glob
11import os
12import platform as platform_lib
13import socket
14import struct
15import subprocess as sp
16import warnings
17
18import winreg
19
20from scapy.arch.windows.structures import (
21    _windows_title,
22    GetAdaptersAddresses,
23    GetIpForwardTable,
24    GetIpForwardTable2,
25    get_service_status,
26)
27from scapy.consts import WINDOWS, WINDOWS_XP
28from scapy.config import conf, ProgPath
29from scapy.error import (
30    Scapy_Exception,
31    log_interactive,
32    log_loading,
33    log_runtime,
34    warning,
35)
36from scapy.interfaces import NetworkInterface, InterfaceProvider, \
37    dev_from_index, resolve_iface, network_name
38from scapy.pton_ntop import inet_ntop
39from scapy.utils import atol, itom, str2mac
40from scapy.utils6 import construct_source_candidate_set, in6_getscope
41from scapy.compat import plain_str
42from scapy.supersocket import SuperSocket
43
44# re-export
45from scapy.arch.common import get_if_raw_addr  # noqa: F401
46
47# Typing imports
48from typing import (
49    Any,
50    Dict,
51    Iterator,
52    List,
53    Optional,
54    Tuple,
55    Type,
56    Union,
57    cast,
58    overload,
59)
60from scapy.compat import Literal
61
62conf.use_pcap = True
63
64# These import must appear after setting conf.use_* variables
65from scapy.arch import libpcap  # noqa: E402
66from scapy.arch.libpcap import (  # noqa: E402
67    NPCAP_PATH,
68    PCAP_IF_UP,
69)
70
71# Detection happens after libpcap import (NPcap detection)
72NPCAP_LOOPBACK_NAME = r"\Device\NPF_Loopback"
73NPCAP_LOOPBACK_NAME_LEGACY = "Npcap Loopback Adapter"  # before npcap 0.9983
74if conf.use_npcap:
75    conf.loopback_name = NPCAP_LOOPBACK_NAME
76else:
77    try:
78        if float(platform_lib.release()) >= 8.1:
79            conf.loopback_name = "Microsoft KM-TEST Loopback Adapter"
80        else:
81            conf.loopback_name = "Microsoft Loopback Adapter"
82    except ValueError:
83        conf.loopback_name = "Microsoft Loopback Adapter"
84
85# hot-patching socket for missing variables on Windows
86if not hasattr(socket, 'IPPROTO_IPIP'):
87    socket.IPPROTO_IPIP = 4  # type: ignore
88if not hasattr(socket, 'IP_RECVTTL'):
89    socket.IP_RECVTTL = 12  # type: ignore
90if not hasattr(socket, 'IPV6_HDRINCL'):
91    socket.IPV6_HDRINCL = 36  # type: ignore
92# https://github.com/python/cpython/issues/73701
93if not hasattr(socket, 'IPPROTO_IPV6'):
94    socket.IPPROTO_IPV6 = 41
95if not hasattr(socket, 'SOL_IPV6'):
96    socket.SOL_IPV6 = socket.IPPROTO_IPV6  # type: ignore
97if not hasattr(socket, 'IPPROTO_GRE'):
98    socket.IPPROTO_GRE = 47  # type: ignore
99if not hasattr(socket, 'IPPROTO_AH'):
100    socket.IPPROTO_AH = 51
101if not hasattr(socket, 'IPPROTO_ESP'):
102    socket.IPPROTO_ESP = 50
103
104_WlanHelper = NPCAP_PATH + "\\WlanHelper.exe"
105
106
107def _encapsulate_admin(cmd):
108    # type: (str) -> str
109    """Encapsulate a command with an Administrator flag"""
110    # To get admin access, we start a new powershell instance with admin
111    # rights, which will execute the command. This needs to be done from a
112    # powershell as we run it from a cmd.
113    # ! Behold !
114    return ("powershell /command \"Start-Process cmd "
115            "-windowstyle hidden -Wait -PassThru -Verb RunAs "
116            "-ArgumentList '/c %s'\"" % cmd)
117
118
119def _get_npcap_config(param_key):
120    # type: (str) -> Optional[str]
121    """
122    Get a Npcap parameter matching key in the registry.
123
124    List:
125    AdminOnly, DefaultFilterSettings, DltNull, Dot11Adapters, Dot11Support
126    LoopbackAdapter, LoopbackSupport, NdisImPlatformBindingOptions, VlanSupport
127    WinPcapCompatible
128    """
129    hkey = winreg.HKEY_LOCAL_MACHINE
130    node = r"SYSTEM\CurrentControlSet\Services\npcap\Parameters"
131    try:
132        key = winreg.OpenKey(hkey, node)
133        dot11_adapters, _ = winreg.QueryValueEx(key, param_key)
134        winreg.CloseKey(key)
135    except WindowsError:
136        return None
137    return cast(str, dot11_adapters)
138
139
140def _where(filename, dirs=None, env="PATH"):
141    # type: (str, Optional[Any], str) -> str
142    """Find file in current dir, in deep_lookup cache or in system path"""
143    if dirs is None:
144        dirs = []
145    if not isinstance(dirs, list):
146        dirs = [dirs]
147    if glob(filename):
148        return filename
149    paths = [os.curdir] + os.environ[env].split(os.path.pathsep) + dirs
150    try:
151        return next(os.path.normpath(match)
152                    for path in paths
153                    for match in glob(os.path.join(path, filename))
154                    if match)
155    except (StopIteration, RuntimeError):
156        raise IOError("File not found: %s" % filename)
157
158
159def win_find_exe(filename, installsubdir=None, env="ProgramFiles"):
160    # type: (str, Optional[Any], str) -> str
161    """Find executable in current dir, system path or in the
162    given ProgramFiles subdir, and retuen its absolute path.
163    """
164    fns = [filename] if filename.endswith(".exe") else [filename + ".exe", filename]  # noqa: E501
165    for fn in fns:
166        try:
167            if installsubdir is None:
168                path = _where(fn)
169            else:
170                path = _where(fn, dirs=[os.path.join(os.environ[env], installsubdir)])  # noqa: E501
171        except IOError:
172            path = None
173        else:
174            break
175    return path or ""
176
177
178class WinProgPath(ProgPath):
179    def __init__(self):
180        # type: () -> None
181        self._reload()
182
183    def _reload(self):
184        # type: () -> None
185        self.pdfreader = ""
186        self.psreader = ""
187        self.svgreader = ""
188        # We try some magic to find the appropriate executables
189        self.dot = win_find_exe("dot")
190        self.tcpdump = win_find_exe("windump")
191        self.tshark = win_find_exe("tshark")
192        self.tcpreplay = win_find_exe("tcpreplay")
193        self.display = self._default
194        self.hexedit = win_find_exe("hexer")
195        self.sox = win_find_exe("sox")
196        self.wireshark = win_find_exe("wireshark", "wireshark")
197        self.extcap_folders = [
198            os.path.join(os.environ.get("appdata", ""), "Wireshark", "extcap"),
199            os.path.join(os.environ.get("programfiles", ""), "Wireshark", "extcap"),
200        ]
201        self.powershell = win_find_exe(
202            "powershell",
203            installsubdir="System32\\WindowsPowerShell\\v1.0",
204            env="SystemRoot"
205        )
206        self.cmd = win_find_exe("cmd", installsubdir="System32",
207                                env="SystemRoot")
208
209
210def _exec_cmd(command):
211    # type: (str) -> Tuple[bytes, int]
212    """Call a CMD command and return the output and returncode"""
213    proc = sp.Popen(command,
214                    stdout=sp.PIPE,
215                    shell=True)
216    res = proc.communicate()[0]
217    return res, proc.returncode
218
219
220conf.prog = WinProgPath()
221
222if conf.prog.tcpdump and conf.use_npcap:
223    def test_windump_npcap():
224        # type: () -> bool
225        """Return whether windump version is correct or not"""
226        try:
227            p_test_windump = sp.Popen([conf.prog.tcpdump, "-help"], stdout=sp.PIPE, stderr=sp.STDOUT)  # noqa: E501
228            stdout, err = p_test_windump.communicate()
229            _windows_title()
230            _output = stdout.lower()
231            return b"npcap" in _output and b"winpcap" not in _output
232        except Exception:
233            return False
234    windump_ok = test_windump_npcap()
235    if not windump_ok:
236        log_loading.warning(
237            "The installed Windump version does not work with Npcap! "
238            "Refer to 'Winpcap/Npcap conflicts' in scapy's installation doc"
239        )
240    del windump_ok
241
242
243def get_windows_if_list(extended=False):
244    # type: (bool) -> List[Dict[str, Any]]
245    """Returns windows interfaces through GetAdaptersAddresses.
246
247    params:
248     - extended: include anycast and multicast IPv6 (default False)"""
249    # Should work on Windows XP+
250    def _get_mac(x):
251        # type: (Dict[str, Any]) -> str
252        size = x["physical_address_length"]
253        if size != 6:
254            return ""
255        data = bytearray(x["physical_address"])
256        return str2mac(bytes(data)[:size])
257
258    def _resolve_ips(y):
259        # type: (List[Dict[str, Any]]) -> List[str]
260        if not isinstance(y, list):
261            return []
262        ips = []
263        for ip in y:
264            addr = ip['address']['address'].contents
265            if addr.si_family == socket.AF_INET6:
266                ip_key = "Ipv6"
267                si_key = "sin6_addr"
268            else:
269                ip_key = "Ipv4"
270                si_key = "sin_addr"
271            data = getattr(addr, ip_key)
272            data = getattr(data, si_key)
273            data = bytes(bytearray(data.byte))
274            # Build IP
275            if data:
276                ips.append(inet_ntop(addr.si_family, data))
277        return ips
278
279    def _get_ips(x):
280        # type: (Dict[str, Any]) -> List[str]
281        unicast = x['first_unicast_address']
282        anycast = x['first_anycast_address']
283        multicast = x['first_multicast_address']
284
285        ips = []
286        ips.extend(_resolve_ips(unicast))
287        if extended:
288            ips.extend(_resolve_ips(anycast))
289            ips.extend(_resolve_ips(multicast))
290        return ips
291
292    return [
293        {
294            "name": plain_str(x["friendly_name"]),
295            "index": x["interface_index"],
296            "description": plain_str(x["description"]),
297            "guid": plain_str(x["adapter_name"]),
298            "mac": _get_mac(x),
299            "type": x["interface_type"],
300            "ipv4_metric": 0 if WINDOWS_XP else x["ipv4_metric"],
301            "ipv6_metric": 0 if WINDOWS_XP else x["ipv6_metric"],
302            "ips": _get_ips(x),
303            "nameservers": _resolve_ips(x["first_dns_server_address"])
304        } for x in GetAdaptersAddresses()
305    ]
306
307
308def _pcapname_to_guid(pcap_name):
309    # type: (str) -> str
310    """Converts a Winpcap/Npcap pcpaname to its guid counterpart.
311    e.g. \\DEVICE\\NPF_{...} => {...}
312    """
313    if "{" in pcap_name:
314        return "{" + pcap_name.split("{")[1]
315    return pcap_name
316
317
318class NetworkInterface_Win(NetworkInterface):
319    """A network interface of your local host"""
320
321    def __init__(self, provider, data=None):
322        # type: (WindowsInterfacesProvider, Optional[Dict[str, Any]]) -> None
323        self.cache_mode = None  # type: Optional[bool]
324        self.ipv4_metric = None  # type: Optional[int]
325        self.ipv6_metric = None  # type: Optional[int]
326        self.nameservers = []  # type: List[str]
327        self.guid = None  # type: Optional[str]
328        self.raw80211 = None  # type: Optional[bool]
329        super(NetworkInterface_Win, self).__init__(provider, data)
330
331    def update(self, data):
332        # type: (Dict[str, Any]) -> None
333        """Update info about a network interface according
334        to a given dictionary. Such data is provided by get_windows_if_list
335        """
336        # Populated early because used below
337        self.network_name = data['network_name']
338        # Windows specific
339        self.guid = data['guid']
340        self.ipv4_metric = data['ipv4_metric']
341        self.ipv6_metric = data['ipv6_metric']
342        self.nameservers = data['nameservers']
343
344        try:
345            # Npcap loopback interface
346            if conf.use_npcap and self.network_name == conf.loopback_name:
347                # https://nmap.org/npcap/guide/npcap-devguide.html
348                data["mac"] = "00:00:00:00:00:00"
349                data["ip"] = "127.0.0.1"
350                data["ip6"] = "::1"
351                data["ips"] = ["127.0.0.1", "::1"]
352        except KeyError:
353            pass
354        super(NetworkInterface_Win, self).update(data)
355
356    def _check_npcap_requirement(self):
357        # type: () -> None
358        if not conf.use_npcap:
359            raise OSError("This operation requires Npcap.")
360        if self.raw80211 is None:
361            val = _get_npcap_config("Dot11Support")
362            self.raw80211 = bool(int(val)) if val else False
363        if not self.raw80211:
364            raise Scapy_Exception("Npcap 802.11 support is NOT enabled !")
365
366    def _npcap_set(self, key, val):
367        # type: (str, str) -> bool
368        """Internal function. Set a [key] parameter to [value]"""
369        if self.guid is None:
370            raise OSError("Interface not setup")
371        res, code = _exec_cmd(_encapsulate_admin(
372            " ".join([_WlanHelper, self.guid[1:-1], key, val])
373        ))
374        _windows_title()  # Reset title of the window
375        if code != 0:
376            raise OSError(res.decode("utf8", errors="ignore"))
377        return True
378
379    def _npcap_get(self, key):
380        # type: (str) -> str
381        if self.guid is None:
382            raise OSError("Interface not setup")
383        res, code = _exec_cmd(" ".join([_WlanHelper, self.guid[1:-1], key]))
384        _windows_title()  # Reset title of the window
385        if code != 0:
386            raise OSError(res.decode("utf8", errors="ignore"))
387        return plain_str(res.strip())
388
389    def mode(self):
390        # type: () -> str
391        """Get the interface operation mode.
392        Only available with Npcap."""
393        self._check_npcap_requirement()
394        return self._npcap_get("mode")
395
396    def ismonitor(self):
397        # type: () -> bool
398        """Returns True if the interface is in monitor mode.
399        Only available with Npcap."""
400        if self.cache_mode is not None:
401            return self.cache_mode
402        try:
403            res = (self.mode() == "monitor")
404            self.cache_mode = res
405            return res
406        except Scapy_Exception:
407            return False
408
409    def setmonitor(self, enable=True):
410        # type: (bool) -> bool
411        """Alias for setmode('monitor') or setmode('managed')
412        Only available with Npcap"""
413        # We must reset the monitor cache
414        if enable:
415            res = self.setmode('monitor')
416        else:
417            res = self.setmode('managed')
418        if not res:
419            log_runtime.error("Npcap WlanHelper returned with an error code !")
420        self.cache_mode = None
421        tmp = self.cache_mode = self.ismonitor()
422        return tmp if enable else (not tmp)
423
424    def availablemodes(self):
425        # type: () -> List[str]
426        """Get all available interface modes.
427        Only available with Npcap."""
428        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11  # noqa: E501
429        self._check_npcap_requirement()
430        return self._npcap_get("modes").split(",")
431
432    def setmode(self, mode):
433        # type: (Union[str, int]) -> bool
434        """Set the interface mode. It can be:
435        - 0 or managed: Managed Mode (aka "Extensible Station Mode")
436        - 1 or monitor: Monitor Mode (aka "Network Monitor Mode")
437        - 2 or master: Master Mode (aka "Extensible Access Point")
438              (supported from Windows 7 and later)
439        - 3 or wfd_device: The Wi-Fi Direct Device operation mode
440              (supported from Windows 8 and later)
441        - 4 or wfd_owner: The Wi-Fi Direct Group Owner operation mode
442              (supported from Windows 8 and later)
443        - 5 or wfd_client: The Wi-Fi Direct Client operation mode
444              (supported from Windows 8 and later)
445        Only available with Npcap."""
446        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11  # noqa: E501
447        self._check_npcap_requirement()
448        _modes = {
449            0: "managed",
450            1: "monitor",
451            2: "master",
452            3: "wfd_device",
453            4: "wfd_owner",
454            5: "wfd_client"
455        }
456        m = _modes.get(mode, "unknown") if isinstance(mode, int) else mode
457        return self._npcap_set("mode", m)
458
459    def channel(self):
460        # type: () -> int
461        """Get the channel of the interface.
462        Only available with Npcap."""
463        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11  # noqa: E501
464        self._check_npcap_requirement()
465        return int(self._npcap_get("channel"))
466
467    def setchannel(self, channel):
468        # type: (int) -> bool
469        """Set the channel of the interface (1-14):
470        Only available with Npcap."""
471        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11  # noqa: E501
472        self._check_npcap_requirement()
473        return self._npcap_set("channel", str(channel))
474
475    def frequency(self):
476        # type: () -> int
477        """Get the frequency of the interface.
478        Only available with Npcap."""
479        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11  # noqa: E501
480        self._check_npcap_requirement()
481        return int(self._npcap_get("freq"))
482
483    def setfrequency(self, freq):
484        # type: (int) -> bool
485        """Set the channel of the interface (1-14):
486        Only available with Npcap."""
487        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11  # noqa: E501
488        self._check_npcap_requirement()
489        return self._npcap_set("freq", str(freq))
490
491    def availablemodulations(self):
492        # type: () -> List[str]
493        """Get all available 802.11 interface modulations.
494        Only available with Npcap."""
495        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11  # noqa: E501
496        self._check_npcap_requirement()
497        return self._npcap_get("modus").split(",")
498
499    def modulation(self):
500        # type: () -> str
501        """Get the 802.11 modulation of the interface.
502        Only available with Npcap."""
503        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11  # noqa: E501
504        self._check_npcap_requirement()
505        return self._npcap_get("modu")
506
507    def setmodulation(self, modu):
508        # type: (int) -> bool
509        """Set the interface modulation. It can be:
510           - 0: dsss
511           - 1: fhss
512           - 2: irbaseband
513           - 3: ofdm
514           - 4: hrdss
515           - 5: erp
516           - 6: ht
517           - 7: vht
518           - 8: ihv
519           - 9: mimo-ofdm
520           - 10: mimo-ofdm
521           - the value directly
522        Only available with Npcap."""
523        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11  # noqa: E501
524        self._check_npcap_requirement()
525        _modus = {
526            0: "dsss",
527            1: "fhss",
528            2: "irbaseband",
529            3: "ofdm",
530            4: "hrdss",
531            5: "erp",
532            6: "ht",
533            7: "vht",
534            8: "ihv",
535            9: "mimo-ofdm",
536            10: "mimo-ofdm",
537        }
538        m = _modus.get(modu, "unknown") if isinstance(modu, int) else modu
539        return self._npcap_set("modu", str(m))
540
541
542class WindowsInterfacesProvider(InterfaceProvider):
543    name = "libpcap"
544    libpcap = True
545
546    def _is_valid(self, dev):
547        # type: (NetworkInterface) -> bool
548        # Winpcap (and old Npcap) have no support for PCAP_IF_UP :(
549        if dev.flags == 0:
550            return True
551        return bool(dev.flags & PCAP_IF_UP)
552
553    @classmethod
554    def _pcap_check(cls):
555        # type: () -> None
556        """Performs checks/restart pcap adapter"""
557        if not conf.use_pcap:
558            # Winpcap/Npcap isn't installed
559            return
560
561        _detect = pcap_service_status()
562
563        def _ask_user():
564            # type: () -> bool
565            if not conf.interactive:
566                return False
567            msg = "Do you want to start it ? (yes/no) [y]: "
568            try:
569                # Better IPython compatibility
570                import IPython
571                return cast(bool, IPython.utils.io.ask_yes_no(msg, default='y'))
572            except (NameError, ImportError):
573                while True:
574                    _confir = input(msg)
575                    _confir = _confir.lower().strip()
576                    if _confir in ["yes", "y", ""]:
577                        return True
578                    elif _confir in ["no", "n"]:
579                        return False
580        if _detect:
581            # No action needed
582            return
583        else:
584            log_interactive.warning(
585                "Scapy has detected that your pcap service is not running !"
586            )
587            if not conf.interactive or _ask_user():
588                succeed = pcap_service_start(askadmin=conf.interactive)
589                if succeed:
590                    log_loading.info("Pcap service started !")
591                    return
592        log_loading.warning(
593            "Could not start the pcap service! "
594            "You probably won't be able to send packets. "
595            "Check your winpcap/npcap installation "
596            "and access rights."
597        )
598
599    def load(self, NetworkInterface_Win=NetworkInterface_Win):
600        # type: (type) -> Dict[str, NetworkInterface]
601        results = {}
602        if not conf.cache_pcapiflist:
603            # Try a restart
604            WindowsInterfacesProvider._pcap_check()
605
606        legacy_npcap_guid = None
607        windows_interfaces = dict()
608        for i in get_windows_if_list():
609            # Only consider interfaces with a GUID
610            if i['guid']:
611                if conf.use_npcap:
612                    # Detect the legacy Loopback interface
613                    if i['name'] == NPCAP_LOOPBACK_NAME_LEGACY:
614                        # Legacy Npcap (<0.9983)
615                        legacy_npcap_guid = i['guid']
616                    elif "Loopback" in i['name']:
617                        # Newer Npcap
618                        i['guid'] = conf.loopback_name
619                # Map interface
620                windows_interfaces[i['guid']] = i
621
622        def iterinterfaces() -> Iterator[
623            Tuple[str, Optional[str], List[str], int, str, Optional[Dict[str, Any]]]
624        ]:
625            if conf.use_pcap:
626                # We have a libpcap provider: enrich pcap interfaces with
627                # Windows data
628                for netw, if_data in conf.cache_pcapiflist.items():
629                    name, ips, flags, _, _ = if_data
630                    guid = _pcapname_to_guid(netw)
631                    if guid == legacy_npcap_guid:
632                        # Legacy Npcap detected !
633                        conf.loopback_name = netw
634                    data = windows_interfaces.get(guid, None)
635                    yield netw, name, ips, flags, guid, data
636            else:
637                # We don't have a libpcap provider: only use Windows data
638                for guid, data in windows_interfaces.items():
639                    netw = r'\Device\NPF_' + guid if guid[0] != '\\' else guid
640                    yield netw, None, [], 0, guid, data
641
642        index = 0
643        for netw, name, ips, flags, guid, data in iterinterfaces():
644            if data:
645                # Exists in Windows registry
646                data['network_name'] = netw
647                data['ips'] = list(set(data['ips'] + ips))
648                data['flags'] = flags
649            else:
650                # Only in [Wi]npcap
651                index -= 1
652                data = {
653                    'name': name,
654                    'description': name,
655                    'index': index,
656                    'guid': guid,
657                    'network_name': netw,
658                    'mac': '00:00:00:00:00:00',
659                    'ipv4_metric': 0,
660                    'ipv6_metric': 0,
661                    'ips': ips,
662                    'flags': flags,
663                    'nameservers': [],
664                }
665            # No KeyError will happen here, as we get it from cache
666            results[netw] = NetworkInterface_Win(self, data)
667        return results
668
669    def reload(self):
670        # type: () -> Dict[str, NetworkInterface]
671        """Reload interface list"""
672        self.restarted_adapter = False
673        if conf.use_pcap:
674            # Reload from Winpcapy
675            from scapy.arch.libpcap import load_winpcapy
676            load_winpcapy()
677        return self.load()
678
679    def _l3socket(self, dev, ipv6):
680        # type: (NetworkInterface, bool) -> Type[SuperSocket]
681        """Return L3 socket used by interfaces of this provider"""
682        if ipv6:
683            return conf.L3socket6
684        else:
685            return conf.L3socket
686
687
688# Register provider
689conf.ifaces.register_provider(WindowsInterfacesProvider)
690
691
692def get_ips(v6=False):
693    # type: (bool) -> Dict[NetworkInterface, List[str]]
694    """Returns all available IPs matching to interfaces, using the windows system.
695    Should only be used as a WinPcapy fallback.
696
697    :param v6: IPv6 addresses
698    """
699    res = {}
700    for iface in conf.ifaces.values():
701        if v6:
702            res[iface] = iface.ips[6]
703        else:
704            res[iface] = iface.ips[4]
705    return res
706
707
708def get_ip_from_name(ifname, v6=False):
709    # type: (str, bool) -> str
710    """Backward compatibility: indirectly calls get_ips
711    Deprecated.
712    """
713    warnings.warn(
714        "get_ip_from_name is deprecated. Use the `ip` attribute of the iface "
715        "or use get_ips() to get all ips per interface.",
716        DeprecationWarning
717    )
718    iface = conf.ifaces.dev_from_name(ifname)
719    return get_ips(v6=v6).get(iface, [""])[0]
720
721
722def pcap_service_name():
723    # type: () -> str
724    """Return the pcap adapter service's name"""
725    return "npcap" if conf.use_npcap else "npf"
726
727
728def pcap_service_status():
729    # type: () -> bool
730    """Returns whether the windows pcap adapter is running or not"""
731    status = get_service_status(pcap_service_name())
732    return status["dwCurrentState"] == 4
733
734
735def _pcap_service_control(action, askadmin=True):
736    # type: (str, bool) -> bool
737    """Internal util to run pcap control command"""
738    command = action + ' ' + pcap_service_name()
739    res, code = _exec_cmd(_encapsulate_admin(command) if askadmin else command)
740    if code != 0:
741        warning(res.decode("utf8", errors="ignore"))
742    return (code == 0)
743
744
745def pcap_service_start(askadmin=True):
746    # type: (bool) -> bool
747    """Starts the pcap adapter. Will ask for admin. Returns True if success"""
748    return _pcap_service_control('sc start', askadmin=askadmin)
749
750
751def pcap_service_stop(askadmin=True):
752    # type: (bool) -> bool
753    """Stops the pcap adapter. Will ask for admin. Returns True if success"""
754    return _pcap_service_control('sc stop', askadmin=askadmin)
755
756
757if conf.use_pcap:
758    _orig_open_pcap = libpcap.open_pcap
759
760    def open_pcap(device,  # type: Union[str, NetworkInterface]
761                  *args,  # type: Any
762                  **kargs  # type: Any
763                  ):
764        # type: (...) -> libpcap._PcapWrapper_libpcap
765        """open_pcap: Windows routine for creating a pcap from an interface.
766        This function is also responsible for detecting monitor mode.
767        """
768        iface = cast(NetworkInterface_Win, resolve_iface(device))
769        iface_network_name = iface.network_name
770        if not iface:
771            raise Scapy_Exception(
772                "Interface is invalid (no pcap match found)!"
773            )
774        # Only check monitor mode when manually specified.
775        # Checking/setting for monitor mode will slow down the process, and the
776        # common is case is not to use monitor mode
777        kw_monitor = kargs.get("monitor", None)
778        if conf.use_npcap and kw_monitor is not None:
779            monitored = iface.ismonitor()
780            if kw_monitor is not monitored:
781                # The monitor param is specified, and not matching the current
782                # interface state
783                iface.setmonitor(kw_monitor)
784        return _orig_open_pcap(iface_network_name, *args, **kargs)
785    libpcap.open_pcap = open_pcap  # type: ignore
786
787
788def _read_routes_c_v1():
789    # type: () -> List[Tuple[int, int, str, str, str, int]]
790    """Retrieve Windows routes through a GetIpForwardTable call.
791
792    This is compatible with XP but won't get IPv6 routes."""
793    def _extract_ip(obj):
794        # type: (int) -> str
795        return inet_ntop(socket.AF_INET, struct.pack("<I", obj))
796
797    def _proc(ip):
798        # type: (int) -> int
799        if WINDOWS_XP:
800            return struct.unpack("<I", struct.pack(">I", ip))[0]
801        return ip
802    routes = []
803    for route in GetIpForwardTable():
804        ifIndex = route['ForwardIfIndex']
805        dest = _proc(route['ForwardDest'])
806        netmask = _proc(route['ForwardMask'])
807        nexthop = _extract_ip(route['ForwardNextHop'])
808        metric = route['ForwardMetric1']
809        # Build route
810        try:
811            iface = cast(NetworkInterface_Win, dev_from_index(ifIndex))
812            if not iface.ip or iface.ip == "0.0.0.0":
813                continue
814        except ValueError:
815            continue
816        ip = iface.ip
817        netw = network_name(iface)
818        # RouteMetric + InterfaceMetric
819        metric = metric + iface.ipv4_metric
820        routes.append((dest, netmask, nexthop, netw, ip, metric))
821    return routes
822
823
824@overload
825def _read_routes_c(ipv6):  # noqa: F811
826    # type: (Literal[True]) -> List[Tuple[str, int, str, str, List[str], int]]
827    pass
828
829
830@overload
831def _read_routes_c(ipv6=False):  # noqa: F811
832    # type: (Literal[False]) -> List[Tuple[int, int, str, str, str, int]]
833    pass
834
835
836def _read_routes_c(ipv6=False):  # noqa: F811
837    # type: (bool) -> Union[List[Tuple[int, int, str, str, str, int]], List[Tuple[str, int, str, str, List[str], int]]]  # noqa: E501
838    """Retrieve Windows routes through a GetIpForwardTable2 call.
839
840    This is not available on Windows XP !"""
841    af = socket.AF_INET6 if ipv6 else socket.AF_INET
842    sock_addr_name = 'Ipv6' if ipv6 else 'Ipv4'
843    sin_addr_name = 'sin6_addr' if ipv6 else 'sin_addr'
844    metric_name = 'ipv6_metric' if ipv6 else 'ipv4_metric'
845    if ipv6:
846        lifaddr = in6_getifaddr()
847    routes = []  # type: List[Any]
848
849    def _extract_ip(obj):
850        # type: (Dict[str, Any]) -> str
851        ip = obj[sock_addr_name][sin_addr_name]
852        ip = bytes(bytearray(ip['byte']))
853        # Build IP
854        return inet_ntop(af, ip)
855
856    for route in GetIpForwardTable2(af):
857        # Extract data
858        ifIndex = route['InterfaceIndex']
859        dest = _extract_ip(route['DestinationPrefix']['Prefix'])
860        netmask = route['DestinationPrefix']['PrefixLength']
861        nexthop = _extract_ip(route['NextHop'])
862        metric = route['Metric']
863        # Build route
864        try:
865            iface = dev_from_index(ifIndex)
866            if not iface.ip or iface.ip == "0.0.0.0":
867                continue
868        except ValueError:
869            continue
870        ip = iface.ip
871        netw = network_name(iface)
872        # RouteMetric + InterfaceMetric
873        metric = metric + getattr(iface, metric_name)
874        if ipv6:
875            _append_route6(routes, dest, netmask, nexthop,
876                           netw, lifaddr, metric)
877        else:
878            routes.append((atol(dest), itom(int(netmask)),
879                           nexthop, netw, ip, metric))
880    return routes
881
882
883def read_routes():
884    # type: () -> List[Tuple[int, int, str, str, str, int]]
885    routes = []
886    try:
887        if WINDOWS_XP:
888            routes = _read_routes_c_v1()
889        else:
890            routes = _read_routes_c(ipv6=False)
891    except Exception as e:
892        log_loading.warning("Error building scapy IPv4 routing table : %s", e)
893    return routes
894
895
896############
897#   IPv6   #
898############
899
900
901def in6_getifaddr():
902    # type: () -> List[Tuple[str, int, str]]
903    """
904    Returns all IPv6 addresses found on the computer
905    """
906    ifaddrs = []  # type: List[Tuple[str, int, str]]
907    ip6s = get_ips(v6=True)
908    for iface, ips in ip6s.items():
909        for ip in ips:
910            scope = in6_getscope(ip)
911            ifaddrs.append((ip, scope, iface.network_name))
912    # Appends Npcap loopback if available
913    if conf.use_npcap and conf.loopback_name:
914        ifaddrs.append(("::1", 0, conf.loopback_name))
915    return ifaddrs
916
917
918def _append_route6(routes,  # type: List[Tuple[str, int, str, str, List[str], int]]
919                   dpref,  # type: str
920                   dp,  # type: int
921                   nh,  # type: str
922                   iface,  # type: str
923                   lifaddr,  # type: List[Tuple[str, int, str]]
924                   metric,  # type: int
925                   ):
926    # type: (...) -> None
927    cset = []  # candidate set (possible source addresses)
928    if iface == conf.loopback_name:
929        if dpref == '::':
930            return
931        cset = ['::1']
932    else:
933        devaddrs = (x for x in lifaddr if x[2] == iface)
934        cset = construct_source_candidate_set(dpref, dp, devaddrs)
935    if not cset:
936        return
937    # APPEND (DESTINATION, NETMASK, NEXT HOP, IFACE, CANDIDATES, METRIC)
938    routes.append((dpref, dp, nh, iface, cset, metric))
939
940
941def read_routes6():
942    # type: () -> List[Tuple[str, int, str, str, List[str], int]]
943    routes6 = []
944    if WINDOWS_XP:
945        return routes6
946    try:
947        routes6 = _read_routes_c(ipv6=True)
948    except Exception as e:
949        log_loading.warning("Error building scapy IPv6 routing table : %s", e)
950    return routes6
951
952
953def _route_add_loopback(routes=None,  # type: Optional[List[Any]]
954                        ipv6=False,  # type: bool
955                        iflist=None,  # type: Optional[List[str]]
956                        ):
957    # type: (...) -> None
958    """Add a route to 127.0.0.1 and ::1 to simplify unit tests on Windows"""
959    if not WINDOWS:
960        warning("Calling _route_add_loopback is only valid on Windows")
961        return
962    warning("This will completely mess up the routes. Testing purpose only !")
963    # Add only if some adapters already exist
964    if ipv6:
965        if not conf.route6.routes:
966            return
967    else:
968        if not conf.route.routes:
969            return
970    conf.ifaces._add_fake_iface(conf.loopback_name)
971    adapter = conf.ifaces.dev_from_name(conf.loopback_name)
972    if iflist:
973        iflist.append(adapter.network_name)
974        return
975    # Remove all conf.loopback_name routes
976    for route in list(conf.route.routes):
977        iface = route[3]
978        if iface == conf.loopback_name:
979            conf.route.routes.remove(route)
980    # Remove conf.loopback_name interface
981    for devname, ifname in list(conf.ifaces.items()):
982        if ifname == conf.loopback_name:
983            conf.ifaces.pop(devname)
984    # Inject interface
985    conf.ifaces[r"\Device\NPF_{0XX00000-X000-0X0X-X00X-00XXXX000XXX}"] = adapter
986    conf.loopback_name = adapter.network_name
987    if isinstance(conf.iface, NetworkInterface):
988        if conf.iface.network_name == conf.loopback_name:
989            conf.iface = adapter
990    conf.netcache.arp_cache["127.0.0.1"] = "ff:ff:ff:ff:ff:ff"  # type: ignore
991    conf.netcache.in6_neighbor["::1"] = "ff:ff:ff:ff:ff:ff"  # type: ignore
992    # Build the packed network addresses
993    loop_net = struct.unpack("!I", socket.inet_aton("127.0.0.0"))[0]
994    loop_mask = struct.unpack("!I", socket.inet_aton("255.0.0.0"))[0]
995    # Build the fake routes
996    loopback_route = (
997        loop_net,
998        loop_mask,
999        "0.0.0.0",
1000        adapter.network_name,
1001        "127.0.0.1",
1002        1
1003    )
1004    loopback_route6 = ('::1', 128, '::', adapter.network_name, ["::1"], 1)
1005    loopback_route6_custom = ("fe80::", 128, "::", adapter.network_name, ["::1"], 1)
1006    if routes is None:
1007        # Injection
1008        conf.route6.routes.append(loopback_route6)
1009        conf.route6.routes.append(loopback_route6_custom)
1010        conf.route.routes.append(loopback_route)
1011        # Flush the caches
1012        conf.route6.invalidate_cache()
1013        conf.route.invalidate_cache()
1014    else:
1015        if ipv6:
1016            routes.append(loopback_route6)
1017            routes.append(loopback_route6_custom)
1018        else:
1019            routes.append(loopback_route)
1020
1021
1022class _NotAvailableSocket(SuperSocket):
1023    desc = "wpcap.dll missing"
1024
1025    def __init__(self, *args, **kargs):
1026        # type: (*Any, **Any) -> None
1027        raise RuntimeError(
1028            "Sniffing and sending packets is not available at layer 2: "
1029            "winpcap is not installed. You may use conf.L3socket or "
1030            "conf.L3socket6 to access layer 3"
1031        )
1032
1033
1034#######
1035# DNS #
1036#######
1037
1038def read_nameservers() -> List[str]:
1039    """Return the nameservers configured by the OS (on the default interface)
1040    """
1041    # Windows has support for different DNS servers on each network interface,
1042    # but to be cross-platform we only return the servers for the default one.
1043    if isinstance(conf.iface, NetworkInterface_Win):
1044        return conf.iface.nameservers
1045    else:
1046        return []
1047