Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
resolver.py2055 linesDownload Raw Back to dns
1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license2 3# Copyright (C) 2003-2017 Nominum, Inc.4#5# Permission to use, copy, modify, and distribute this software and its6# documentation for any purpose with or without fee is hereby granted,7# provided that the above copyright notice and this permission notice8# appear in all copies.9#10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.17 18"""DNS stub resolver."""19 20import contextlib21import random22import socket23import sys24import threading25import time26import warnings27from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union28from urllib.parse import urlparse29 30import dns._ddr31import dns.edns32import dns.exception33import dns.flags34import dns.inet35import dns.ipv436import dns.ipv637import dns.message38import dns.name39import dns.nameserver40import dns.query41import dns.rcode42import dns.rdataclass43import dns.rdatatype44import dns.rdtypes.svcbbase45import dns.reversename46import dns.tsig47 48if sys.platform == "win32":49    import dns.win32util50 51 52class NXDOMAIN(dns.exception.DNSException):53    """The DNS query name does not exist."""54 55    supp_kwargs = {"qnames", "responses"}56    fmt = None  # we have our own __str__ implementation57 58    # pylint: disable=arguments-differ59 60    # We do this as otherwise mypy complains about unexpected keyword argument61    # idna_exception62    def __init__(self, *args, **kwargs):63        super().__init__(*args, **kwargs)64 65    def _check_kwargs(self, qnames, responses=None):66        if not isinstance(qnames, (list, tuple, set)):67            raise AttributeError("qnames must be a list, tuple or set")68        if len(qnames) == 0:69            raise AttributeError("qnames must contain at least one element")70        if responses is None:71            responses = {}72        elif not isinstance(responses, dict):73            raise AttributeError("responses must be a dict(qname=response)")74        kwargs = dict(qnames=qnames, responses=responses)75        return kwargs76 77    def __str__(self) -> str:78        if "qnames" not in self.kwargs:79            return super().__str__()80        qnames = self.kwargs["qnames"]81        if len(qnames) > 1:82            msg = "None of DNS query names exist"83        else:84            msg = "The DNS query name does not exist"85        qnames = ", ".join(map(str, qnames))86        return "{}: {}".format(msg, qnames)87 88    @property89    def canonical_name(self):90        """Return the unresolved canonical name."""91        if "qnames" not in self.kwargs:92            raise TypeError("parametrized exception required")93        for qname in self.kwargs["qnames"]:94            response = self.kwargs["responses"][qname]95            try:96                cname = response.canonical_name()97                if cname != qname:98                    return cname99            except Exception:100                # We can just eat this exception as it means there was101                # something wrong with the response.102                pass103        return self.kwargs["qnames"][0]104 105    def __add__(self, e_nx):106        """Augment by results from another NXDOMAIN exception."""107        qnames0 = list(self.kwargs.get("qnames", []))108        responses0 = dict(self.kwargs.get("responses", {}))109        responses1 = e_nx.kwargs.get("responses", {})110        for qname1 in e_nx.kwargs.get("qnames", []):111            if qname1 not in qnames0:112                qnames0.append(qname1)113            if qname1 in responses1:114                responses0[qname1] = responses1[qname1]115        return NXDOMAIN(qnames=qnames0, responses=responses0)116 117    def qnames(self):118        """All of the names that were tried.119 120        Returns a list of ``dns.name.Name``.121        """122        return self.kwargs["qnames"]123 124    def responses(self):125        """A map from queried names to their NXDOMAIN responses.126 127        Returns a dict mapping a ``dns.name.Name`` to a128        ``dns.message.Message``.129        """130        return self.kwargs["responses"]131 132    def response(self, qname):133        """The response for query *qname*.134 135        Returns a ``dns.message.Message``.136        """137        return self.kwargs["responses"][qname]138 139 140class YXDOMAIN(dns.exception.DNSException):141    """The DNS query name is too long after DNAME substitution."""142 143 144ErrorTuple = Tuple[145    Optional[str],146    bool,147    int,148    Union[Exception, str],149    Optional[dns.message.Message],150]151 152 153def _errors_to_text(errors: List[ErrorTuple]) -> List[str]:154    """Turn a resolution errors trace into a list of text."""155    texts = []156    for err in errors:157        texts.append("Server {} answered {}".format(err[0], err[3]))158    return texts159 160 161class LifetimeTimeout(dns.exception.Timeout):162    """The resolution lifetime expired."""163 164    msg = "The resolution lifetime expired."165    fmt = "%s after {timeout:.3f} seconds: {errors}" % msg[:-1]166    supp_kwargs = {"timeout", "errors"}167 168    # We do this as otherwise mypy complains about unexpected keyword argument169    # idna_exception170    def __init__(self, *args, **kwargs):171        super().__init__(*args, **kwargs)172 173    def _fmt_kwargs(self, **kwargs):174        srv_msgs = _errors_to_text(kwargs["errors"])175        return super()._fmt_kwargs(176            timeout=kwargs["timeout"], errors="; ".join(srv_msgs)177        )178 179 180# We added more detail to resolution timeouts, but they are still181# subclasses of dns.exception.Timeout for backwards compatibility.  We also182# keep dns.resolver.Timeout defined for backwards compatibility.183Timeout = LifetimeTimeout184 185 186class NoAnswer(dns.exception.DNSException):187    """The DNS response does not contain an answer to the question."""188 189    fmt = "The DNS response does not contain an answer to the question: {query}"190    supp_kwargs = {"response"}191 192    # We do this as otherwise mypy complains about unexpected keyword argument193    # idna_exception194    def __init__(self, *args, **kwargs):195        super().__init__(*args, **kwargs)196 197    def _fmt_kwargs(self, **kwargs):198        return super()._fmt_kwargs(query=kwargs["response"].question)199 200    def response(self):201        return self.kwargs["response"]202 203 204class NoNameservers(dns.exception.DNSException):205    """All nameservers failed to answer the query.206 207    errors: list of servers and respective errors208    The type of errors is209    [(server IP address, any object convertible to string)].210    Non-empty errors list will add explanatory message ()211    """212 213    msg = "All nameservers failed to answer the query."214    fmt = "%s {query}: {errors}" % msg[:-1]215    supp_kwargs = {"request", "errors"}216 217    # We do this as otherwise mypy complains about unexpected keyword argument218    # idna_exception219    def __init__(self, *args, **kwargs):220        super().__init__(*args, **kwargs)221 222    def _fmt_kwargs(self, **kwargs):223        srv_msgs = _errors_to_text(kwargs["errors"])224        return super()._fmt_kwargs(225            query=kwargs["request"].question, errors="; ".join(srv_msgs)226        )227 228 229class NotAbsolute(dns.exception.DNSException):230    """An absolute domain name is required but a relative name was provided."""231 232 233class NoRootSOA(dns.exception.DNSException):234    """There is no SOA RR at the DNS root name. This should never happen!"""235 236 237class NoMetaqueries(dns.exception.DNSException):238    """DNS metaqueries are not allowed."""239 240 241class NoResolverConfiguration(dns.exception.DNSException):242    """Resolver configuration could not be read or specified no nameservers."""243 244 245class Answer:246    """DNS stub resolver answer.247 248    Instances of this class bundle up the result of a successful DNS249    resolution.250 251    For convenience, the answer object implements much of the sequence252    protocol, forwarding to its ``rrset`` attribute.  E.g.253    ``for a in answer`` is equivalent to ``for a in answer.rrset``.254    ``answer[i]`` is equivalent to ``answer.rrset[i]``, and255    ``answer[i:j]`` is equivalent to ``answer.rrset[i:j]``.256 257    Note that CNAMEs or DNAMEs in the response may mean that answer258    RRset's name might not be the query name.259    """260 261    def __init__(262        self,263        qname: dns.name.Name,264        rdtype: dns.rdatatype.RdataType,265        rdclass: dns.rdataclass.RdataClass,266        response: dns.message.QueryMessage,267        nameserver: Optional[str] = None,268        port: Optional[int] = None,269    ) -> None:270        self.qname = qname271        self.rdtype = rdtype272        self.rdclass = rdclass273        self.response = response274        self.nameserver = nameserver275        self.port = port276        self.chaining_result = response.resolve_chaining()277        # Copy some attributes out of chaining_result for backwards278        # compatibility and convenience.279        self.canonical_name = self.chaining_result.canonical_name280        self.rrset = self.chaining_result.answer281        self.expiration = time.time() + self.chaining_result.minimum_ttl282 283    def __getattr__(self, attr):  # pragma: no cover284        if attr == "name":285            return self.rrset.name286        elif attr == "ttl":287            return self.rrset.ttl288        elif attr == "covers":289            return self.rrset.covers290        elif attr == "rdclass":291            return self.rrset.rdclass292        elif attr == "rdtype":293            return self.rrset.rdtype294        else:295            raise AttributeError(attr)296 297    def __len__(self) -> int:298        return self.rrset and len(self.rrset) or 0299 300    def __iter__(self):301        return self.rrset and iter(self.rrset) or iter(tuple())302 303    def __getitem__(self, i):304        if self.rrset is None:305            raise IndexError306        return self.rrset[i]307 308    def __delitem__(self, i):309        if self.rrset is None:310            raise IndexError311        del self.rrset[i]312 313 314class Answers(dict):315    """A dict of DNS stub resolver answers, indexed by type."""316 317 318class HostAnswers(Answers):319    """A dict of DNS stub resolver answers to a host name lookup, indexed by320    type.321    """322 323    @classmethod324    def make(325        cls,326        v6: Optional[Answer] = None,327        v4: Optional[Answer] = None,328        add_empty: bool = True,329    ) -> "HostAnswers":330        answers = HostAnswers()331        if v6 is not None and (add_empty or v6.rrset):332            answers[dns.rdatatype.AAAA] = v6333        if v4 is not None and (add_empty or v4.rrset):334            answers[dns.rdatatype.A] = v4335        return answers336 337    # Returns pairs of (address, family) from this result, potentiallys338    # filtering by address family.339    def addresses_and_families(340        self, family: int = socket.AF_UNSPEC341    ) -> Iterator[Tuple[str, int]]:342        if family == socket.AF_UNSPEC:343            yield from self.addresses_and_families(socket.AF_INET6)344            yield from self.addresses_and_families(socket.AF_INET)345            return346        elif family == socket.AF_INET6:347            answer = self.get(dns.rdatatype.AAAA)348        elif family == socket.AF_INET:349            answer = self.get(dns.rdatatype.A)350        else:351            raise NotImplementedError(f"unknown address family {family}")352        if answer:353            for rdata in answer:354                yield (rdata.address, family)355 356    # Returns addresses from this result, potentially filtering by357    # address family.358    def addresses(self, family: int = socket.AF_UNSPEC) -> Iterator[str]:359        return (pair[0] for pair in self.addresses_and_families(family))360 361    # Returns the canonical name from this result.362    def canonical_name(self) -> dns.name.Name:363        answer = self.get(dns.rdatatype.AAAA, self.get(dns.rdatatype.A))364        return answer.canonical_name365 366 367class CacheStatistics:368    """Cache Statistics"""369 370    def __init__(self, hits: int = 0, misses: int = 0) -> None:371        self.hits = hits372        self.misses = misses373 374    def reset(self) -> None:375        self.hits = 0376        self.misses = 0377 378    def clone(self) -> "CacheStatistics":379        return CacheStatistics(self.hits, self.misses)380 381 382class CacheBase:383    def __init__(self) -> None:384        self.lock = threading.Lock()385        self.statistics = CacheStatistics()386 387    def reset_statistics(self) -> None:388        """Reset all statistics to zero."""389        with self.lock:390            self.statistics.reset()391 392    def hits(self) -> int:393        """How many hits has the cache had?"""394        with self.lock:395            return self.statistics.hits396 397    def misses(self) -> int:398        """How many misses has the cache had?"""399        with self.lock:400            return self.statistics.misses401 402    def get_statistics_snapshot(self) -> CacheStatistics:403        """Return a consistent snapshot of all the statistics.404 405        If running with multiple threads, it's better to take a406        snapshot than to call statistics methods such as hits() and407        misses() individually.408        """409        with self.lock:410            return self.statistics.clone()411 412 413CacheKey = Tuple[dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass]414 415 416class Cache(CacheBase):417    """Simple thread-safe DNS answer cache."""418 419    def __init__(self, cleaning_interval: float = 300.0) -> None:420        """*cleaning_interval*, a ``float`` is the number of seconds between421        periodic cleanings.422        """423 424        super().__init__()425        self.data: Dict[CacheKey, Answer] = {}426        self.cleaning_interval = cleaning_interval427        self.next_cleaning: float = time.time() + self.cleaning_interval428 429    def _maybe_clean(self) -> None:430        """Clean the cache if it's time to do so."""431 432        now = time.time()433        if self.next_cleaning <= now:434            keys_to_delete = []435            for k, v in self.data.items():436                if v.expiration <= now:437                    keys_to_delete.append(k)438            for k in keys_to_delete:439                del self.data[k]440            now = time.time()441            self.next_cleaning = now + self.cleaning_interval442 443    def get(self, key: CacheKey) -> Optional[Answer]:444        """Get the answer associated with *key*.445 446        Returns None if no answer is cached for the key.447 448        *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)``449        tuple whose values are the query name, rdtype, and rdclass respectively.450 451        Returns a ``dns.resolver.Answer`` or ``None``.452        """453 454        with self.lock:455            self._maybe_clean()456            v = self.data.get(key)457            if v is None or v.expiration <= time.time():458                self.statistics.misses += 1459                return None460            self.statistics.hits += 1461            return v462 463    def put(self, key: CacheKey, value: Answer) -> None:464        """Associate key and value in the cache.465 466        *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)``467        tuple whose values are the query name, rdtype, and rdclass respectively.468 469        *value*, a ``dns.resolver.Answer``, the answer.470        """471 472        with self.lock:473            self._maybe_clean()474            self.data[key] = value475 476    def flush(self, key: Optional[CacheKey] = None) -> None:477        """Flush the cache.478 479        If *key* is not ``None``, only that item is flushed.  Otherwise the entire cache480        is flushed.481 482        *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)``483        tuple whose values are the query name, rdtype, and rdclass respectively.484        """485 486        with self.lock:487            if key is not None:488                if key in self.data:489                    del self.data[key]490            else:491                self.data = {}492                self.next_cleaning = time.time() + self.cleaning_interval493 494 495class LRUCacheNode:496    """LRUCache node."""497 498    def __init__(self, key, value):499        self.key = key500        self.value = value501        self.hits = 0502        self.prev = self503        self.next = self504 505    def link_after(self, node: "LRUCacheNode") -> None:506        self.prev = node507        self.next = node.next508        node.next.prev = self509        node.next = self510 511    def unlink(self) -> None:512        self.next.prev = self.prev513        self.prev.next = self.next514 515 516class LRUCache(CacheBase):517    """Thread-safe, bounded, least-recently-used DNS answer cache.518 519    This cache is better than the simple cache (above) if you're520    running a web crawler or other process that does a lot of521    resolutions.  The LRUCache has a maximum number of nodes, and when522    it is full, the least-recently used node is removed to make space523    for a new one.524    """525 526    def __init__(self, max_size: int = 100000) -> None:527        """*max_size*, an ``int``, is the maximum number of nodes to cache;528        it must be greater than 0.529        """530 531        super().__init__()532        self.data: Dict[CacheKey, LRUCacheNode] = {}533        self.set_max_size(max_size)534        self.sentinel: LRUCacheNode = LRUCacheNode(None, None)535        self.sentinel.prev = self.sentinel536        self.sentinel.next = self.sentinel537 538    def set_max_size(self, max_size: int) -> None:539        if max_size < 1:540            max_size = 1541        self.max_size = max_size542 543    def get(self, key: CacheKey) -> Optional[Answer]:544        """Get the answer associated with *key*.545 546        Returns None if no answer is cached for the key.547 548        *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)``549        tuple whose values are the query name, rdtype, and rdclass respectively.550 551        Returns a ``dns.resolver.Answer`` or ``None``.552        """553 554        with self.lock:555            node = self.data.get(key)556            if node is None:557                self.statistics.misses += 1558                return None559            # Unlink because we're either going to move the node to the front560            # of the LRU list or we're going to free it.561            node.unlink()562            if node.value.expiration <= time.time():563                del self.data[node.key]564                self.statistics.misses += 1565                return None566            node.link_after(self.sentinel)567            self.statistics.hits += 1568            node.hits += 1569            return node.value570 571    def get_hits_for_key(self, key: CacheKey) -> int:572        """Return the number of cache hits associated with the specified key."""573        with self.lock:574            node = self.data.get(key)575            if node is None or node.value.expiration <= time.time():576                return 0577            else:578                return node.hits579 580    def put(self, key: CacheKey, value: Answer) -> None:581        """Associate key and value in the cache.582 583        *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)``584        tuple whose values are the query name, rdtype, and rdclass respectively.585 586        *value*, a ``dns.resolver.Answer``, the answer.587        """588 589        with self.lock:590            node = self.data.get(key)591            if node is not None:592                node.unlink()593                del self.data[node.key]594            while len(self.data) >= self.max_size:595                gnode = self.sentinel.prev596                gnode.unlink()597                del self.data[gnode.key]598            node = LRUCacheNode(key, value)599            node.link_after(self.sentinel)600            self.data[key] = node601 602    def flush(self, key: Optional[CacheKey] = None) -> None:603        """Flush the cache.604 605        If *key* is not ``None``, only that item is flushed.  Otherwise the entire cache606        is flushed.607 608        *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)``609        tuple whose values are the query name, rdtype, and rdclass respectively.610        """611 612        with self.lock:613            if key is not None:614                node = self.data.get(key)615                if node is not None:616                    node.unlink()617                    del self.data[node.key]618            else:619                gnode = self.sentinel.next620                while gnode != self.sentinel:621                    next = gnode.next622                    gnode.unlink()623                    gnode = next624                self.data = {}625 626 627class _Resolution:628    """Helper class for dns.resolver.Resolver.resolve().629 630    All of the "business logic" of resolution is encapsulated in this631    class, allowing us to have multiple resolve() implementations632    using different I/O schemes without copying all of the633    complicated logic.634 635    This class is a "friend" to dns.resolver.Resolver and manipulates636    resolver data structures directly.637    """638 639    def __init__(640        self,641        resolver: "BaseResolver",642        qname: Union[dns.name.Name, str],643        rdtype: Union[dns.rdatatype.RdataType, str],644        rdclass: Union[dns.rdataclass.RdataClass, str],645        tcp: bool,646        raise_on_no_answer: bool,647        search: Optional[bool],648    ) -> None:649        if isinstance(qname, str):650            qname = dns.name.from_text(qname, None)651        rdtype = dns.rdatatype.RdataType.make(rdtype)652        if dns.rdatatype.is_metatype(rdtype):653            raise NoMetaqueries654        rdclass = dns.rdataclass.RdataClass.make(rdclass)655        if dns.rdataclass.is_metaclass(rdclass):656            raise NoMetaqueries657        self.resolver = resolver658        self.qnames_to_try = resolver._get_qnames_to_try(qname, search)659        self.qnames = self.qnames_to_try[:]660        self.rdtype = rdtype661        self.rdclass = rdclass662        self.tcp = tcp663        self.raise_on_no_answer = raise_on_no_answer664        self.nxdomain_responses: Dict[dns.name.Name, dns.message.QueryMessage] = {}665        # Initialize other things to help analysis tools666        self.qname = dns.name.empty667        self.nameservers: List[dns.nameserver.Nameserver] = []668        self.current_nameservers: List[dns.nameserver.Nameserver] = []669        self.errors: List[ErrorTuple] = []670        self.nameserver: Optional[dns.nameserver.Nameserver] = None671        self.tcp_attempt = False672        self.retry_with_tcp = False673        self.request: Optional[dns.message.QueryMessage] = None674        self.backoff = 0.0675 676    def next_request(677        self,678    ) -> Tuple[Optional[dns.message.QueryMessage], Optional[Answer]]:679        """Get the next request to send, and check the cache.680 681        Returns a (request, answer) tuple.  At most one of request or682        answer will not be None.683        """684 685        # We return a tuple instead of Union[Message,Answer] as it lets686        # the caller avoid isinstance().687 688        while len(self.qnames) > 0:689            self.qname = self.qnames.pop(0)690 691            # Do we know the answer?692            if self.resolver.cache:693                answer = self.resolver.cache.get(694                    (self.qname, self.rdtype, self.rdclass)695                )696                if answer is not None:697                    if answer.rrset is None and self.raise_on_no_answer:698                        raise NoAnswer(response=answer.response)699                    else:700                        return (None, answer)701                answer = self.resolver.cache.get(702                    (self.qname, dns.rdatatype.ANY, self.rdclass)703                )704                if answer is not None and answer.response.rcode() == dns.rcode.NXDOMAIN:705                    # cached NXDOMAIN; record it and continue to next706                    # name.707                    self.nxdomain_responses[self.qname] = answer.response708                    continue709 710            # Build the request711            request = dns.message.make_query(self.qname, self.rdtype, self.rdclass)712            if self.resolver.keyname is not None:713                request.use_tsig(714                    self.resolver.keyring,715                    self.resolver.keyname,716                    algorithm=self.resolver.keyalgorithm,717                )718            request.use_edns(719                self.resolver.edns,720                self.resolver.ednsflags,721                self.resolver.payload,722                options=self.resolver.ednsoptions,723            )724            if self.resolver.flags is not None:725                request.flags = self.resolver.flags726 727            self.nameservers = self.resolver._enrich_nameservers(728                self.resolver._nameservers,729                self.resolver.nameserver_ports,730                self.resolver.port,731            )732            if self.resolver.rotate:733                random.shuffle(self.nameservers)734            self.current_nameservers = self.nameservers[:]735            self.errors = []736            self.nameserver = None737            self.tcp_attempt = False738            self.retry_with_tcp = False739            self.request = request740            self.backoff = 0.10741 742            return (request, None)743 744        #745        # We've tried everything and only gotten NXDOMAINs.  (We know746        # it's only NXDOMAINs as anything else would have returned747        # before now.)748        #749        raise NXDOMAIN(qnames=self.qnames_to_try, responses=self.nxdomain_responses)750 751    def next_nameserver(self) -> Tuple[dns.nameserver.Nameserver, bool, float]:752        if self.retry_with_tcp:753            assert self.nameserver is not None754            assert not self.nameserver.is_always_max_size()755            self.tcp_attempt = True756            self.retry_with_tcp = False757            return (self.nameserver, True, 0)758 759        backoff = 0.0760        if not self.current_nameservers:761            if len(self.nameservers) == 0:762                # Out of things to try!763                raise NoNameservers(request=self.request, errors=self.errors)764            self.current_nameservers = self.nameservers[:]765            backoff = self.backoff766            self.backoff = min(self.backoff * 2, 2)767 768        self.nameserver = self.current_nameservers.pop(0)769        self.tcp_attempt = self.tcp or self.nameserver.is_always_max_size()770        return (self.nameserver, self.tcp_attempt, backoff)771 772    def query_result(773        self, response: Optional[dns.message.Message], ex: Optional[Exception]774    ) -> Tuple[Optional[Answer], bool]:775        #776        # returns an (answer: Answer, end_loop: bool) tuple.777        #778        assert self.nameserver is not None779        if ex:780            # Exception during I/O or from_wire()781            assert response is None782            self.errors.append(783                (784                    str(self.nameserver),785                    self.tcp_attempt,786                    self.nameserver.answer_port(),787                    ex,788                    response,789                )790            )791            if (792                isinstance(ex, dns.exception.FormError)793                or isinstance(ex, EOFError)794                or isinstance(ex, OSError)795                or isinstance(ex, NotImplementedError)796            ):797                # This nameserver is no good, take it out of the mix.798                self.nameservers.remove(self.nameserver)799            elif isinstance(ex, dns.message.Truncated):800                if self.tcp_attempt:801                    # Truncation with TCP is no good!802                    self.nameservers.remove(self.nameserver)803                else:804                    self.retry_with_tcp = True805            return (None, False)806        # We got an answer!807        assert response is not None808        assert isinstance(response, dns.message.QueryMessage)809        rcode = response.rcode()810        if rcode == dns.rcode.NOERROR:811            try:812                answer = Answer(813                    self.qname,814                    self.rdtype,815                    self.rdclass,816                    response,817                    self.nameserver.answer_nameserver(),818                    self.nameserver.answer_port(),819                )820            except Exception as e:821                self.errors.append(822                    (823                        str(self.nameserver),824                        self.tcp_attempt,825                        self.nameserver.answer_port(),826                        e,827                        response,828                    )829                )830                # The nameserver is no good, take it out of the mix.831                self.nameservers.remove(self.nameserver)832                return (None, False)833            if self.resolver.cache:834                self.resolver.cache.put((self.qname, self.rdtype, self.rdclass), answer)835            if answer.rrset is None and self.raise_on_no_answer:836                raise NoAnswer(response=answer.response)837            return (answer, True)838        elif rcode == dns.rcode.NXDOMAIN:839            # Further validate the response by making an Answer, even840            # if we aren't going to cache it.841            try:842                answer = Answer(843                    self.qname, dns.rdatatype.ANY, dns.rdataclass.IN, response844                )845            except Exception as e:846                self.errors.append(847                    (848                        str(self.nameserver),849                        self.tcp_attempt,850                        self.nameserver.answer_port(),851                        e,852                        response,853                    )854                )855                # The nameserver is no good, take it out of the mix.856                self.nameservers.remove(self.nameserver)857                return (None, False)858            self.nxdomain_responses[self.qname] = response859            if self.resolver.cache:860                self.resolver.cache.put(861                    (self.qname, dns.rdatatype.ANY, self.rdclass), answer862                )863            # Make next_nameserver() return None, so caller breaks its864            # inner loop and calls next_request().865            return (None, True)866        elif rcode == dns.rcode.YXDOMAIN:867            yex = YXDOMAIN()868            self.errors.append(869                (870                    str(self.nameserver),871                    self.tcp_attempt,872                    self.nameserver.answer_port(),873                    yex,874                    response,875                )876            )877            raise yex878        else:879            #880            # We got a response, but we're not happy with the881            # rcode in it.882            #883            if rcode != dns.rcode.SERVFAIL or not self.resolver.retry_servfail:884                self.nameservers.remove(self.nameserver)885            self.errors.append(886                (887                    str(self.nameserver),888                    self.tcp_attempt,889                    self.nameserver.answer_port(),890                    dns.rcode.to_text(rcode),891                    response,892                )893            )894            return (None, False)895 896 897class BaseResolver:898    """DNS stub resolver."""899 900    # We initialize in reset()901    #902    # pylint: disable=attribute-defined-outside-init903 904    domain: dns.name.Name905    nameserver_ports: Dict[str, int]906    port: int907    search: List[dns.name.Name]908    use_search_by_default: bool909    timeout: float910    lifetime: float911    keyring: Optional[Any]912    keyname: Optional[Union[dns.name.Name, str]]913    keyalgorithm: Union[dns.name.Name, str]914    edns: int915    ednsflags: int916    ednsoptions: Optional[List[dns.edns.Option]]917    payload: int918    cache: Any919    flags: Optional[int]920    retry_servfail: bool921    rotate: bool922    ndots: Optional[int]923    _nameservers: Sequence[Union[str, dns.nameserver.Nameserver]]924 925    def __init__(926        self, filename: str = "/etc/resolv.conf", configure: bool = True927    ) -> None:928        """*filename*, a ``str`` or file object, specifying a file929        in standard /etc/resolv.conf format.  This parameter is meaningful930        only when *configure* is true and the platform is POSIX.931 932        *configure*, a ``bool``.  If True (the default), the resolver933        instance is configured in the normal fashion for the operating934        system the resolver is running on.  (I.e. by reading a935        /etc/resolv.conf file on POSIX systems and from the registry936        on Windows systems.)937        """938 939        self.reset()940        if configure:941            if sys.platform == "win32":942                self.read_registry()943            elif filename:944                self.read_resolv_conf(filename)945 946    def reset(self) -> None:947        """Reset all resolver configuration to the defaults."""948 949        self.domain = dns.name.Name(dns.name.from_text(socket.gethostname())[1:])950        if len(self.domain) == 0:951            self.domain = dns.name.root952        self._nameservers = []953        self.nameserver_ports = {}954        self.port = 53955        self.search = []956        self.use_search_by_default = False957        self.timeout = 2.0958        self.lifetime = 5.0959        self.keyring = None960        self.keyname = None961        self.keyalgorithm = dns.tsig.default_algorithm962        self.edns = -1963        self.ednsflags = 0964        self.ednsoptions = None965        self.payload = 0966        self.cache = None967        self.flags = None968        self.retry_servfail = False969        self.rotate = False970        self.ndots = None971 972    def read_resolv_conf(self, f: Any) -> None:973        """Process *f* as a file in the /etc/resolv.conf format.  If f is974        a ``str``, it is used as the name of the file to open; otherwise it975        is treated as the file itself.976 977        Interprets the following items:978 979        - nameserver - name server IP address980 981        - domain - local domain name982 983        - search - search list for host-name lookup984 985        - options - supported options are rotate, timeout, edns0, and ndots986 987        """988 989        nameservers = []990        if isinstance(f, str):991            try:992                cm: contextlib.AbstractContextManager = open(f)993            except OSError:994                # /etc/resolv.conf doesn't exist, can't be read, etc.995                raise NoResolverConfiguration(f"cannot open {f}")996        else:997            cm = contextlib.nullcontext(f)998        with cm as f:999            for l in f:1000                if len(l) == 0 or l[0] == "#" or l[0] == ";":1001                    continue1002                tokens = l.split()1003 1004                # Any line containing less than 2 tokens is malformed1005                if len(tokens) < 2:1006                    continue1007 1008                if tokens[0] == "nameserver":1009                    nameservers.append(tokens[1])1010                elif tokens[0] == "domain":1011                    self.domain = dns.name.from_text(tokens[1])1012                    # domain and search are exclusive1013                    self.search = []1014                elif tokens[0] == "search":1015                    # the last search wins1016                    self.search = []1017                    for suffix in tokens[1:]:1018                        self.search.append(dns.name.from_text(suffix))1019                    # We don't set domain as it is not used if1020                    # len(self.search) > 01021                elif tokens[0] == "options":1022                    for opt in tokens[1:]:1023                        if opt == "rotate":1024                            self.rotate = True1025                        elif opt == "edns0":1026                            self.use_edns()1027                        elif "timeout" in opt:1028                            try:1029                                self.timeout = int(opt.split(":")[1])1030                            except (ValueError, IndexError):1031                                pass1032                        elif "ndots" in opt:1033                            try:1034                                self.ndots = int(opt.split(":")[1])1035                            except (ValueError, IndexError):1036                                pass1037        if len(nameservers) == 0:1038            raise NoResolverConfiguration("no nameservers")1039        # Assigning directly instead of appending means we invoke the1040        # setter logic, with additonal checking and enrichment.1041        self.nameservers = nameservers1042 1043    def read_registry(self) -> None:1044        """Extract resolver configuration from the Windows registry."""1045        try:1046            info = dns.win32util.get_dns_info()  # type: ignore1047            if info.domain is not None:1048                self.domain = info.domain1049            self.nameservers = info.nameservers1050            self.search = info.search1051        except AttributeError:1052            raise NotImplementedError1053 1054    def _compute_timeout(1055        self,1056        start: float,1057        lifetime: Optional[float] = None,1058        errors: Optional[List[ErrorTuple]] = None,1059    ) -> float:1060        lifetime = self.lifetime if lifetime is None else lifetime1061        now = time.time()1062        duration = now - start1063        if errors is None:1064            errors = []1065        if duration < 0:1066            if duration < -1:1067                # Time going backwards is bad.  Just give up.1068                raise LifetimeTimeout(timeout=duration, errors=errors)1069            else:1070                # Time went backwards, but only a little.  This can1071                # happen, e.g. under vmware with older linux kernels.1072                # Pretend it didn't happen.1073                duration = 01074        if duration >= lifetime:1075            raise LifetimeTimeout(timeout=duration, errors=errors)1076        return min(lifetime - duration, self.timeout)1077 1078    def _get_qnames_to_try(1079        self, qname: dns.name.Name, search: Optional[bool]1080    ) -> List[dns.name.Name]:1081        # This is a separate method so we can unit test the search1082        # rules without requiring the Internet.1083        if search is None:1084            search = self.use_search_by_default1085        qnames_to_try = []1086        if qname.is_absolute():1087            qnames_to_try.append(qname)1088        else:1089            abs_qname = qname.concatenate(dns.name.root)1090            if search:1091                if len(self.search) > 0:1092                    # There is a search list, so use it exclusively1093                    search_list = self.search[:]1094                elif self.domain != dns.name.root and self.domain is not None:1095                    # We have some notion of a domain that isn't the root, so1096                    # use it as the search list.1097                    search_list = [self.domain]1098                else:1099                    search_list = []1100                # Figure out the effective ndots (default is 1)1101                if self.ndots is None:1102                    ndots = 11103                else:1104                    ndots = self.ndots1105                for suffix in search_list:1106                    qnames_to_try.append(qname + suffix)1107                if len(qname) > ndots:1108                    # The name has at least ndots dots, so we should try an1109                    # absolute query first.1110                    qnames_to_try.insert(0, abs_qname)1111                else:1112                    # The name has less than ndots dots, so we should search1113                    # first, then try the absolute name.1114                    qnames_to_try.append(abs_qname)1115            else:1116                qnames_to_try.append(abs_qname)1117        return qnames_to_try1118 1119    def use_tsig(1120        self,1121        keyring: Any,1122        keyname: Optional[Union[dns.name.Name, str]] = None,1123        algorithm: Union[dns.name.Name, str] = dns.tsig.default_algorithm,1124    ) -> None:1125        """Add a TSIG signature to each query.1126 1127        The parameters are passed to ``dns.message.Message.use_tsig()``;1128        see its documentation for details.1129        """1130 1131        self.keyring = keyring1132        self.keyname = keyname1133        self.keyalgorithm = algorithm1134 1135    def use_edns(1136        self,1137        edns: Optional[Union[int, bool]] = 0,1138        ednsflags: int = 0,1139        payload: int = dns.message.DEFAULT_EDNS_PAYLOAD,1140        options: Optional[List[dns.edns.Option]] = None,1141    ) -> None:1142        """Configure EDNS behavior.1143 1144        *edns*, an ``int``, is the EDNS level to use.  Specifying1145        ``None``, ``False``, or ``-1`` means "do not use EDNS", and in this case1146        the other parameters are ignored.  Specifying ``True`` is1147        equivalent to specifying 0, i.e. "use EDNS0".1148 1149        *ednsflags*, an ``int``, the EDNS flag values.1150 1151        *payload*, an ``int``, is the EDNS sender's payload field, which is the1152        maximum size of UDP datagram the sender can handle.  I.e. how big1153        a response to this message can be.1154 1155        *options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS1156        options.1157        """1158 1159        if edns is None or edns is False:1160            edns = -11161        elif edns is True:1162            edns = 01163        self.edns = edns1164        self.ednsflags = ednsflags1165        self.payload = payload1166        self.ednsoptions = options1167 1168    def set_flags(self, flags: int) -> None:1169        """Overrides the default flags with your own.1170 1171        *flags*, an ``int``, the message flags to use.1172        """1173 1174        self.flags = flags1175 1176    @classmethod1177    def _enrich_nameservers(1178        cls,1179        nameservers: Sequence[Union[str, dns.nameserver.Nameserver]],1180        nameserver_ports: Dict[str, int],1181        default_port: int,1182    ) -> List[dns.nameserver.Nameserver]:1183        enriched_nameservers = []1184        if isinstance(nameservers, list):1185            for nameserver in nameservers:1186                enriched_nameserver: dns.nameserver.Nameserver1187                if isinstance(nameserver, dns.nameserver.Nameserver):1188                    enriched_nameserver = nameserver1189                elif dns.inet.is_address(nameserver):1190                    port = nameserver_ports.get(nameserver, default_port)1191                    enriched_nameserver = dns.nameserver.Do53Nameserver(1192                        nameserver, port1193                    )1194                else:1195                    try:1196                        if urlparse(nameserver).scheme != "https":1197                            raise NotImplementedError1198                    except Exception:1199                        raise ValueError(1200                            f"nameserver {nameserver} is not a "

Showing the first 1,200 of 2055 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai