URI: 
       tdns_hacks.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tdns_hacks.py (4285B)
       ---
            1 # Copyright (C) 2020 The Electrum developers
            2 # Distributed under the MIT software license, see the accompanying
            3 # file LICENCE or http://www.opensource.org/licenses/mit-license.php
            4 
            5 import sys
            6 import socket
            7 import concurrent
            8 from concurrent import futures
            9 import ipaddress
           10 from typing import Optional
           11 
           12 import dns
           13 import dns.resolver
           14 
           15 from .logging import get_logger
           16 
           17 
           18 _logger = get_logger(__name__)
           19 
           20 _dns_threads_executor = None  # type: Optional[concurrent.futures.Executor]
           21 
           22 
           23 def configure_dns_depending_on_proxy(is_proxy: bool) -> None:
           24     # Store this somewhere so we can un-monkey-patch:
           25     if not hasattr(socket, "_getaddrinfo"):
           26         socket._getaddrinfo = socket.getaddrinfo
           27     if is_proxy:
           28         # prevent dns leaks, see http://stackoverflow.com/questions/13184205/dns-over-proxy
           29         socket.getaddrinfo = lambda *args: [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))]
           30     else:
           31         if sys.platform == 'win32':
           32             # On Windows, socket.getaddrinfo takes a mutex, and might hold it for up to 10 seconds
           33             # when dns-resolving. To speed it up drastically, we resolve dns ourselves, outside that lock.
           34             # See https://github.com/spesmilo/electrum/issues/4421
           35             try:
           36                 _prepare_windows_dns_hack()
           37             except Exception as e:
           38                 _logger.exception('failed to apply windows dns hack.')
           39             else:
           40                 socket.getaddrinfo = _fast_getaddrinfo
           41         else:
           42             socket.getaddrinfo = socket._getaddrinfo
           43 
           44 
           45 def _prepare_windows_dns_hack():
           46     # enable dns cache
           47     resolver = dns.resolver.get_default_resolver()
           48     if resolver.cache is None:
           49         resolver.cache = dns.resolver.Cache()
           50     # ensure overall timeout for requests is long enough
           51     resolver.lifetime = max(resolver.lifetime or 1, 30.0)
           52     # prepare threads
           53     global _dns_threads_executor
           54     if _dns_threads_executor is None:
           55         _dns_threads_executor = concurrent.futures.ThreadPoolExecutor(max_workers=20,
           56                                                                       thread_name_prefix='dns_resolver')
           57 
           58 
           59 def _fast_getaddrinfo(host, *args, **kwargs):
           60     def needs_dns_resolving(host):
           61         try:
           62             ipaddress.ip_address(host)
           63             return False  # already valid IP
           64         except ValueError:
           65             pass  # not an IP
           66         if str(host) in ('localhost', 'localhost.',):
           67             return False
           68         return True
           69 
           70     def resolve_with_dnspython(host):
           71         addrs = []
           72         expected_errors = (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
           73                            concurrent.futures.CancelledError, concurrent.futures.TimeoutError)
           74         ipv6_fut = _dns_threads_executor.submit(dns.resolver.resolve, host, dns.rdatatype.AAAA)
           75         ipv4_fut = _dns_threads_executor.submit(dns.resolver.resolve, host, dns.rdatatype.A)
           76         # try IPv6
           77         try:
           78             answers = ipv6_fut.result()
           79             addrs += [str(answer) for answer in answers]
           80         except expected_errors as e:
           81             pass
           82         except BaseException as e:
           83             _logger.info(f'dnspython failed to resolve dns (AAAA) for {repr(host)} with error: {repr(e)}')
           84         # try IPv4
           85         try:
           86             answers = ipv4_fut.result()
           87             addrs += [str(answer) for answer in answers]
           88         except expected_errors as e:
           89             # dns failed for some reason, e.g. dns.resolver.NXDOMAIN this is normal.
           90             # Simply report back failure; except if we already have some results.
           91             if not addrs:
           92                 raise socket.gaierror(11001, 'getaddrinfo failed') from e
           93         except BaseException as e:
           94             # Possibly internal error in dnspython :( see #4483 and #5638
           95             _logger.info(f'dnspython failed to resolve dns (A) for {repr(host)} with error: {repr(e)}')
           96         if addrs:
           97             return addrs
           98         # Fall back to original socket.getaddrinfo to resolve dns.
           99         return [host]
          100 
          101     addrs = [host]
          102     if needs_dns_resolving(host):
          103         addrs = resolve_with_dnspython(host)
          104     list_of_list_of_socketinfos = [socket._getaddrinfo(addr, *args, **kwargs) for addr in addrs]
          105     list_of_socketinfos = [item for lst in list_of_list_of_socketinfos for item in lst]
          106     return list_of_socketinfos