Esempio n. 1
0
def repeated_query(qname, rdtype,
                   nattempts=3,
                   resolver=None,
                   require_ad=False):
    """
    Repeatedly fire a DNS query until either the number of allowed attempts
    (``nattempts``) is excedeed or a result is found.

    ``qname`` must be the (IDNA encoded, as :class:`bytes`) name to query,
    ``rdtype`` the record type to query for. If `resolver` is not :data:`None`,
    it must be a DNSPython :class:`dns.resolver.Resolver` instance; if it is
    :data:`None`, the current default resolver is used.

    If `require_ad` is :data:`True`, the peer resolver is asked to do DNSSEC
    validation and if the AD flag is missing in the response,
    :class:`ValueError` is raised.

    The resolution automatically starts using the TCP transport after the first
    attempt.

    If no result is received before the number of allowed attempts is exceeded,
    :class:`TimeoutError` is raised.

    Return the result set or :data:`None` if the domain does not exist.
    """

    if nattempts <= 0:
        raise ValueError("Query cannot succeed with zero or less attempts")

    resolver = resolver or dns.resolver.get_default_resolver()
    for i in range(nattempts):
        try:
            if require_ad:
                resolver.set_flags(dns.flags.AD | dns.flags.RD)
            else:
                resolver.set_flags(None)
            answer = resolver.query(
                qname.decode("ascii"),
                rdtype,
                tcp=(i > 0),
            )
            if require_ad:
                if not (answer.response.flags & dns.flags.AD):
                    raise ValueError("DNSSEC validation not available")
            break
        except (TimeoutError, dns.resolver.Timeout):
            if i == 0:
                logger.warn("DNS is timing out, switching to TCP")
        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
            return None
    else:
        raise TimeoutError("SRV query timed out")

    return answer
Esempio n. 2
0
    def make_query(self,
                   domain,
                   rdatatype,
                   resolver,
                   debug=False,
                   noRec=False):
        """
        @param domain: domain name to issue query against
        @type domain: L{string}

        @param rdatatype: type of RRSet queried for
        @type rdatatype: L{dns.rdatatype}

        @param resolver: resolver to ask to answer the query
        @type resolver: L{dns.resolver.Resolver}

        @param debug: if True, print some debug output
        @type debug: L{boolean}

        @param noRec: No Recursion (set the RD flag in the query)
        @type noRec: L{Boolean}
        """
        name = self.Str_to_Name(domain)
        if not name:
            return name

        if noRec:
            flags = resolver.flags
            flags &= ~dns.flags.RD
            resolver.set_flags(dns.flags.RD)

        ans = None
        try:
            ans = resolver.query(name,
                                 rdatatype,
                                 rdclass=dns.rdataclass.IN,
                                 tcp=resolver.use_tcp,
                                 raise_on_no_answer=False)
            if debug:
                self.println("%s %s: %s" % (
                    domain,
                    dns.rdatatype.to_text(rdatatype),
                    ans.response.to_text(),
                ))
        except dns.exception.Timeout:
            self.saw_timeout = True
            if debug: self.println("Exception: timeout")
        except dns.resolver.NoAnswer:
            if debug: self.println("Exception: %s" % (sys.exc_info()[0], ))
        except:
            if debug: self.println("Exception: %s" % (sys.exc_info()[0], ))

        return ans
Esempio n. 3
0
def get_resolver(addresses=None, lifetime=5, payload=1420):
    """
    Return resolver object configured to use given list of addresses, and
    that sets DO=1, RD=1, AD=1, and EDNS payload for queries to the resolver.
    """

    resolver = dns.resolver.Resolver()
    resolver.set_flags(dns.flags.RD | dns.flags.AD)
    resolver.use_edns(edns=0, ednsflags=dns.flags.DO, payload=payload)
    resolver.lifetime = lifetime
    if addresses is not None:
        resolver.nameservers = addresses
    return resolver
Esempio n. 4
0
 def run(self):
     arguments = self.arguments.__dict__
     nameservers = arguments.get("nameservers")
     resolver = dns.resolver.Resolver(arguments.get("filename"),
                                      arguments.get("configure_resolver"))
     resolver.set_flags(arguments.get("flags"))
     resolver.use_edns(arguments.get("edns"), arguments.get("edns_flags"),
                       arguments.get("edns_payload"))
     if nameservers:
         resolver.nameservers = nameservers
     resolver.port = arguments.get("port")
     resolver.timeout = arguments.get("timeout")
     resolver.lifetime = arguments.get("lifetime")
     resolver.retry_servfail = arguments.get("retry_servfail")
     if arguments.pop("metaquery"):
         kwargs = {
             v: arguments.get(k)
             for k, v in {
                 "rdclass": "rdclass",
                 "edns": "use_edns",
                 "want_dnssec": "want_dnssec",
                 "edns_flags": "ednsflags",
                 "edns_payload": "request_payload"
             }.items()
         }
         message = dns.message.make_query(arguments.get("query"),
                                          arguments.get("rdtype"), **kwargs)
         kwargs = {
             k: arguments.get(k)
             for k in [
                 "timeout", "port", "source", "source_port",
                 "one_rr_per_rrset"
             ]
         }
         if arguments.get("tcp"):
             resp = dns.query.tcp(message, resolver.nameservers[0],
                                  **kwargs)
         else:
             resp = dns.query.udp(message, resolver.nameservers[0],
                                  **kwargs)
         print(resp)
     else:
         kwargs = {
             k: arguments.get(k)
             for k in ["rdtype", "rdclass", "tcp", "source", "source_port"]
         }
         answer = resolver.query(arguments.pop("query"), **kwargs)
         print(answer.response)
def process_cds_records(obj, dry_run=True):
    domain = get_single_attr(obj, "domain")
    print(f"Domain: {domain}")
    lm = get_single_attr(obj, "last-modified")
    lm = datetime.datetime.strptime(lm, "%Y-%m-%dT%H:%M:%SZ")
    lm = lm.replace(tzinfo=datetime.timezone.utc)
    ripe_ds_rdataset = set(get_attrs(obj, "ds-rdata"))
    print(f"RIPE rdataset: {ripe_ds_rdataset}")

    resolver = dns.resolver.Resolver()
    resolver.set_flags(dns.flags.RD | dns.flags.AD)
    resolver.use_edns(0, dns.flags.DO, 512)
    try:
        a = resolver.query(domain + ".", "CDS")
        assert a.response.rcode() == 0, "DNS response failure"
        assert a.response.flags & dns.flags.AD, "Unauthenticated DNS response"
        asig = a.response.find_rrset(
            a.response.answer,
            a.qname,
            a.rdclass,
            dns.rdatatype.RRSIG,
            a.rdtype,
        )
        inception = datetime.datetime.fromtimestamp(
            asig[0].inception,
            datetime.timezone.utc,
        )
        dns_ds_rdataset = {rd.to_text() for rd in a}
        print(f"DNS  rdataset: {dns_ds_rdataset}")
        print(f"Inception: {inception}, last modified: {lm}")
        assert inception > lm, "Signature inception too early"
        if dns_ds_rdataset and dns_ds_rdataset != ripe_ds_rdataset:
            delete_ds_rdata(obj)
            if not (len(a) == 1 and  # Special Delete DS record
                    a[0].key_tag == 0 and a[0].algorithm == 0 and
                    a[0].digest_type == 0 and a[0].digest == b'\x00'):
                append_ds_rdata(obj, dns_ds_rdataset)
            print("updating DB record")
            o = put_object_to_ripe_db(obj, c.UPDATER_PW, dry_run=dry_run)
            print_rpsl_object(o)

    except dns.exception.DNSException as e:
        print(f"DNS exception: {e}")
    except AssertionError as e:
        print(f"Assertion error: {e}")
Esempio n. 6
0
    def make_query(self, domain, rdatatype, resolver, debug=False, noRec=False):
        """
        @param domain: domain name to issue query against
        @type domain: L{string}

        @param rdatatype: type of RRSet queried for
        @type rdatatype: L{dns.rdatatype}

        @param resolver: resolver to ask to answer the query
        @type resolver: L{dns.resolver.Resolver}

        @param debug: if True, print some debug output
        @type debug: L{boolean}

        @param noRec: No Recursion (set the RD flag in the query)
        @type noRec: L{Boolean}
        """
        name = self.Str_to_Name(domain)
        if not name:
            return name
        
        if noRec:
            flags = resolver.flags
            flags &= ~dns.flags.RD
            resolver.set_flags(dns.flags.RD)

        ans = None
        try:
            ans = resolver.query(name, rdatatype, rdclass=dns.rdataclass.IN, tcp=resolver.use_tcp, raise_on_no_answer=False)
            if debug: self.println("%s %s: %s" % (domain, dns.rdatatype.to_text(rdatatype), ans.response.to_text(), ))
        except dns.exception.Timeout:
            self.saw_timeout = True
            if debug: self.println("Exception: timeout")
        except dns.resolver.NoAnswer:
            if debug: self.println("Exception: %s" % (sys.exc_info()[0], ))
        except:
            if debug: self.println("Exception: %s" % (sys.exc_info()[0], ))

        return ans
Esempio n. 7
0
def repeated_query(qname, rdtype,
                   nattempts=None,
                   resolver=None,
                   require_ad=False,
                   executor=None):
    """
    Repeatedly fire a DNS query until either the number of allowed attempts
    (`nattempts`) is excedeed or a non-error result is returned (NXDOMAIN is
    a non-error result).

    If `nattempts` is :data:`None`, it is set to 3 if `resolver` is
    :data:`None` and to 2 otherwise. This way, no query is made without a
    possible change to a local parameter. (When using the thread-local
    resolver, it will be re-configured after the first failed query and after
    the second failed query, TCP is used. With a fixed resolver, TCP is used
    after the first failed query.)

    `qname` must be the (IDNA encoded, as :class:`bytes`) name to query,
    `rdtype` the record type to query for. If `resolver` is not :data:`None`,
    it must be a DNSPython :class:`dns.resolver.Resolver` instance; if it is
    :data:`None`, the resolver obtained from :func:`get_resolver` is used.

    If `require_ad` is :data:`True`, the peer resolver is asked to do DNSSEC
    validation and if the AD flag is missing in the response,
    :class:`ValueError` is raised. If `require_ad` is :data:`False`, the
    resolver is asked to do DNSSEC validation nevertheless, but missing
    validation (in constrast to failed validation) is not an error.

    .. note::

       This function modifies the flags of the `resolver` instance, no matter
       if it uses the thread-local resolver instance or the resolver passed as
       an argument.

    If the first query fails and `resolver` is :data:`None` and the
    thread-local resolver has not been overridden with :func:`set_resolver`,
    :func:`reconfigure_resolver` is called and the query is re-attempted
    immediately.

    If the next query after reconfiguration of the resolver (if the
    preconditions for resolver reconfigurations are not met, this applies to
    the first failing query), :func:`repeated_query` switches to TCP.

    If no result is received before the number of allowed attempts is exceeded,
    :class:`TimeoutError` is raised.

    Return the result set or :data:`None` if the domain does not exist.

    This is a coroutine; the query is executed in an `executor` using the
    :meth:`asyncio.BaseEventLoop.run_in_executor` of the current event loop. By
    default, the default executor provided by the event loop is used, but it
    can be overridden using the `executor` argument.

    If the used resolver raises :class:`dns.resolver.NoNameservers`
    (semantically, that no nameserver was able to answer the request), this
    function suspects that DNSSEC validation failed, as responding with
    SERVFAIL is what unbound does. To test that case, a simple check is made:
    the query is repeated, but with a flag set which indicates that we would
    like to do the validation ourselves. If that query succeeds, we assume that
    the error is in fact due to DNSSEC validation failure and raise
    :class:`ValidationError`. Otherwise, the answer is discarded and the
    :class:`~dns.resolver.NoNameservers` exception is treated as normal
    timeout. If the exception re-occurs in the second query, it is re-raised,
    as it indicates a serious configuration problem.
    """
    global _state

    loop = asyncio.get_event_loop()

    # tlr = thread-local resolver
    use_tlr = False
    if resolver is None:
        resolver = get_resolver()
        use_tlr = not _state.overridden_resolver

    if nattempts is None:
        if use_tlr:
            nattempts = 3
        else:
            nattempts = 2

    if nattempts <= 0:
        raise ValueError("query cannot succeed with non-positive amount "
                         "of attempts")

    qname = qname.decode("ascii")

    def handle_timeout():
        nonlocal use_tlr, resolver, use_tcp
        if use_tlr and i == 0:
            reconfigure_resolver()
            resolver = get_resolver()
        else:
            use_tcp = True

    use_tcp = False
    for i in range(nattempts):
        resolver.set_flags(dns.flags.RD | dns.flags.AD)
        try:
            answer = yield from loop.run_in_executor(
                executor,
                functools.partial(
                    resolver.query,
                    qname,
                    rdtype,
                    tcp=use_tcp
                )
            )

            if require_ad and not (answer.response.flags & dns.flags.AD):
                raise ValueError("DNSSEC validation not available")
        except (TimeoutError, dns.resolver.Timeout):
            handle_timeout()
            continue
        except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
            return None
        except (dns.resolver.NoNameservers):
            resolver.set_flags(dns.flags.RD | dns.flags.AD | dns.flags.CD)
            try:
                yield from loop.run_in_executor(
                    executor,
                    functools.partial(
                        resolver.query,
                        qname,
                        rdtype,
                        tcp=use_tcp,
                        raise_on_no_answer=False
                    ))
            except (dns.resolver.Timeout, TimeoutError):
                handle_timeout()
                continue
            except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
                pass
            raise ValidationError(
                "nameserver error, most likely DNSSEC validation failed",
            )
        break
    else:
        raise TimeoutError()

    return answer
Esempio n. 8
0
def repeated_query(qname, rdtype,
                   nattempts=None,
                   resolver=None,
                   require_ad=False,
                   executor=None):
    """
    Repeatedly fire a DNS query until either the number of allowed attempts
    (`nattempts`) is excedeed or a non-error result is returned (NXDOMAIN is
    a non-error result).

    If `nattempts` is :data:`None`, it is set to 3 if `resolver` is
    :data:`None` and to 2 otherwise. This way, no query is made without a
    possible change to a local parameter. (When using the thread-local
    resolver, it will be re-configured after the first failed query and after
    the second failed query, TCP is used. With a fixed resolver, TCP is used
    after the first failed query.)

    `qname` must be the (IDNA encoded, as :class:`bytes`) name to query,
    `rdtype` the record type to query for. If `resolver` is not :data:`None`,
    it must be a DNSPython :class:`dns.resolver.Resolver` instance; if it is
    :data:`None`, the resolver obtained from :func:`get_resolver` is used.

    If `require_ad` is :data:`True`, the peer resolver is asked to do DNSSEC
    validation and if the AD flag is missing in the response,
    :class:`ValueError` is raised. If `require_ad` is :data:`False`, the
    resolver is asked to do DNSSEC validation nevertheless, but missing
    validation (in constrast to failed validation) is not an error.

    .. note::

       This function modifies the flags of the `resolver` instance, no matter
       if it uses the thread-local resolver instance or the resolver passed as
       an argument.

    If the first query fails and `resolver` is :data:`None` and the
    thread-local resolver has not been overridden with :func:`set_resolver`,
    :func:`reconfigure_resolver` is called and the query is re-attempted
    immediately.

    If the next query after reconfiguration of the resolver (if the
    preconditions for resolver reconfigurations are not met, this applies to
    the first failing query), :func:`repeated_query` switches to TCP.

    If no result is received before the number of allowed attempts is exceeded,
    :class:`TimeoutError` is raised.

    Return the result set or :data:`None` if the domain does not exist.

    This is a coroutine; the query is executed in an `executor` using the
    :meth:`asyncio.BaseEventLoop.run_in_executor` of the current event loop. By
    default, the default executor provided by the event loop is used, but it
    can be overridden using the `executor` argument.

    If the used resolver raises :class:`dns.resolver.NoNameservers`
    (semantically, that no nameserver was able to answer the request), this
    function suspects that DNSSEC validation failed, as responding with
    SERVFAIL is what unbound does. To test that case, a simple check is made:
    the query is repeated, but with a flag set which indicates that we would
    like to do the validation ourselves. If that query succeeds, we assume that
    the error is in fact due to DNSSEC validation failure and raise
    :class:`ValidationError`. Otherwise, the answer is discarded and the
    :class:`~dns.resolver.NoNameservers` exception is treated as normal
    timeout. If the exception re-occurs in the second query, it is re-raised,
    as it indicates a serious configuration problem.
    """
    global _state

    loop = asyncio.get_event_loop()

    # tlr = thread-local resolver
    use_tlr = False
    if resolver is None:
        resolver = get_resolver()
        use_tlr = not _state.overridden_resolver

    if nattempts is None:
        if use_tlr:
            nattempts = 3
        else:
            nattempts = 2

    if nattempts <= 0:
        raise ValueError("query cannot succeed with non-positive amount "
                         "of attempts")

    qname = qname.decode("ascii")

    def handle_timeout():
        nonlocal use_tlr, resolver, use_tcp
        if use_tlr and i == 0:
            reconfigure_resolver()
            resolver = get_resolver()
        else:
            use_tcp = True

    use_tcp = False
    for i in range(nattempts):
        resolver.set_flags(dns.flags.RD | dns.flags.AD)
        try:
            answer = yield from loop.run_in_executor(
                executor,
                functools.partial(
                    resolver.query,
                    qname,
                    rdtype,
                    tcp=use_tcp
                )
            )

            if require_ad and not (answer.response.flags & dns.flags.AD):
                raise ValueError("DNSSEC validation not available")
        except (TimeoutError, dns.resolver.Timeout):
            handle_timeout()
            continue
        except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
            return None
        except (dns.resolver.NoNameservers):
            resolver.set_flags(dns.flags.RD | dns.flags.AD | dns.flags.CD)
            try:
                yield from loop.run_in_executor(
                    executor,
                    functools.partial(
                        resolver.query,
                        qname,
                        rdtype,
                        tcp=use_tcp,
                        raise_on_no_answer=False
                    ))
            except (dns.resolver.Timeout, TimeoutError):
                handle_timeout()
                continue
            except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
                pass
            raise ValidationError(
                "nameserver error, most likely DNSSEC validation failed",
            )
        break
    else:
        raise TimeoutError()

    return answer
Esempio n. 9
0
def parse_args(args: list = sys.argv[1:]):
    parser = argparse.ArgumentParser("dnask", description=__doc__)
    parser.add_argument("query", type=str, help="Query string.")
    parser.add_argument("-t",
                        "--rdtype",
                        type=str,
                        default=1,
                        help="Query type.")
    parser.add_argument("-c",
                        "--rdclass",
                        type=str,
                        default=1,
                        help="Query class.")
    parser.add_argument("-m",
                        "--metaquery",
                        action="store_true",
                        help="Execute as MetaQuery.")
    parser.add_argument("-s",
                        "--source",
                        type=str,
                        default=socket.gethostbyname(socket.gethostname()),
                        help="Source address.")
    parser.add_argument("-sP",
                        "--source-port",
                        type=int,
                        default=random.randint(1, 65535),
                        help="Source port.")
    parser.add_argument("--tcp",
                        action="store_true",
                        help="Use TCP to make the query.")
    parser.add_argument(
        "-ns",
        "--nameservers",
        nargs="+",
        type=str,
        help=
        "A list of nameservers to query. Each nameserver is a string which contains the IP address of a nameserver."
    )
    parser.add_argument(
        "-p",
        "--port",
        type=int,
        default=53,
        help="The port to which to send queries (Defaults to 53).")
    parser.add_argument(
        "-T",
        "--timeout",
        type=int,
        default=8,
        help=
        "The number of seconds to wait for a response from a server, before timing out."
    )
    parser.add_argument(
        "-l",
        "--lifetime",
        type=int,
        default=8,
        help=
        "The total number of seconds to spend trying to get an answer to the question. If the lifetime expires, a Timeout exception will occur."
    )
    parser.add_argument(
        "-e",
        "--edns",
        type=int,
        default=-1,
        help="The EDNS level to use (Defaults to -1, no Edns).")
    parser.add_argument("-eF",
                        "--edns-flags",
                        type=int,
                        help="The EDNS flags.")
    parser.add_argument("-eP",
                        "--edns-payload",
                        type=int,
                        default=0,
                        help="The EDNS payload size (Defaults to 0).")
    parser.add_argument("-S",
                        "--want-dnssec",
                        action="store_true",
                        help="Indicate that DNSSEC is desired.")
    parser.add_argument(
        "-f",
        "--flags",
        type=int,
        default=None,
        help=
        "The message flags to use (Defaults to None (i.e. not overwritten)).")
    parser.add_argument("-r",
                        "--retry-servfail",
                        action="store_true",
                        help="Retry a nameserver if it says SERVFAIL.")
    parser.add_argument(
        "-R",
        "--one-rr-per-rrset",
        action="store_true",
        help=
        "Put each RR into its own RRset (Only useful when executing MetaQueries)."
    )
    parser.add_argument(
        "--filename",
        type=argparse.FileType("r"),
        help=
        "The filename of a configuration file in standard /etc/resolv.conf format. This parameter is meaningful only when I{configure} is true and the platform is POSIX."
    )
    parser.add_argument(
        "--configure-resolver",
        action="store_false",
        help=
        "If True (the default), the resolver instance is configured in the normal fashion for the operating system the resolver is running on. (I.e. a /etc/resolv.conf file on POSIX systems and from the registry on Windows systems."
    )
    args = parser.parse_args(args).__dict__
    nameservers = args.get("nameservers")

    resolver = dns.resolver.Resolver(args.get("filename"),
                                     args.get("configure_resolver"))
    resolver.set_flags(args.get("flags"))
    resolver.use_edns(args.get("edns"), args.get("edns_flags"),
                      args.get("edns_payload"))
    if not nameservers:
        nameservers = resolver.nameservers
    resolver.nameservers = nameservers
    resolver.port = args.get("port")
    resolver.timeout = args.get("timeout")
    resolver.lifetime = args.get("lifetime")
    resolver.retry_servfail = args.get("retry_servfail")
    if args.pop("metaquery"):
        kwargs = {
            v: args.get(k)
            for k, v in {
                "rdclass": "rdclass",
                "edns": "use_edns",
                "want_dnssec": "want_dnssec",
                "edns_flags": "ednsflags",
                "edns_payload": "request_payload"
            }.items()
        }
        message = dns.message.make_query(args.get("query"), args.get("rdtype"),
                                         **kwargs)
        kwargs = {
            k: args.get(k)
            for k in
            ["timeout", "port", "source", "source_port", "one_rr_per_rrset"]
        }
        if args.get("tcp"):
            resp = dns.query.tcp(message, nameservers[0], **kwargs)
        else:
            resp = dns.query.udp(message, nameservers[0], **kwargs)
        print(colored(resp))
    else:
        kwargs = {
            k: args.get(k)
            for k in ["rdtype", "rdclass", "tcp", "source", "source_port"]
        }
        answer = resolver.query(args.pop("query"), **kwargs)
        print(colored(answer.response))