Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
asyncquery.py781 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"""Talk to a DNS server."""19 20import base6421import contextlib22import socket23import struct24import time25from typing import Any, Dict, Optional, Tuple, Union26 27import dns.asyncbackend28import dns.exception29import dns.inet30import dns.message31import dns.name32import dns.quic33import dns.rcode34import dns.rdataclass35import dns.rdatatype36import dns.transaction37from dns._asyncbackend import NullContext38from dns.query import (39    BadResponse,40    NoDOH,41    NoDOQ,42    UDPMode,43    _compute_times,44    _make_dot_ssl_context,45    _matches_destination,46    _remaining,47    have_doh,48    ssl,49)50 51if have_doh:52    import httpx53 54# for brevity55_lltuple = dns.inet.low_level_address_tuple56 57 58def _source_tuple(af, address, port):59    # Make a high level source tuple, or return None if address and port60    # are both None61    if address or port:62        if address is None:63            if af == socket.AF_INET:64                address = "0.0.0.0"65            elif af == socket.AF_INET6:66                address = "::"67            else:68                raise NotImplementedError(f"unknown address family {af}")69        return (address, port)70    else:71        return None72 73 74def _timeout(expiration, now=None):75    if expiration is not None:76        if not now:77            now = time.time()78        return max(expiration - now, 0)79    else:80        return None81 82 83async def send_udp(84    sock: dns.asyncbackend.DatagramSocket,85    what: Union[dns.message.Message, bytes],86    destination: Any,87    expiration: Optional[float] = None,88) -> Tuple[int, float]:89    """Send a DNS message to the specified UDP socket.90 91    *sock*, a ``dns.asyncbackend.DatagramSocket``.92 93    *what*, a ``bytes`` or ``dns.message.Message``, the message to send.94 95    *destination*, a destination tuple appropriate for the address family96    of the socket, specifying where to send the query.97 98    *expiration*, a ``float`` or ``None``, the absolute time at which99    a timeout exception should be raised.  If ``None``, no timeout will100    occur.  The expiration value is meaningless for the asyncio backend, as101    asyncio's transport sendto() never blocks.102 103    Returns an ``(int, float)`` tuple of bytes sent and the sent time.104    """105 106    if isinstance(what, dns.message.Message):107        what = what.to_wire()108    sent_time = time.time()109    n = await sock.sendto(what, destination, _timeout(expiration, sent_time))110    return (n, sent_time)111 112 113async def receive_udp(114    sock: dns.asyncbackend.DatagramSocket,115    destination: Optional[Any] = None,116    expiration: Optional[float] = None,117    ignore_unexpected: bool = False,118    one_rr_per_rrset: bool = False,119    keyring: Optional[Dict[dns.name.Name, dns.tsig.Key]] = None,120    request_mac: Optional[bytes] = b"",121    ignore_trailing: bool = False,122    raise_on_truncation: bool = False,123    ignore_errors: bool = False,124    query: Optional[dns.message.Message] = None,125) -> Any:126    """Read a DNS message from a UDP socket.127 128    *sock*, a ``dns.asyncbackend.DatagramSocket``.129 130    See :py:func:`dns.query.receive_udp()` for the documentation of the other131    parameters, and exceptions.132 133    Returns a ``(dns.message.Message, float, tuple)`` tuple of the received message, the134    received time, and the address where the message arrived from.135    """136 137    wire = b""138    while True:139        (wire, from_address) = await sock.recvfrom(65535, _timeout(expiration))140        if not _matches_destination(141            sock.family, from_address, destination, ignore_unexpected142        ):143            continue144        received_time = time.time()145        try:146            r = dns.message.from_wire(147                wire,148                keyring=keyring,149                request_mac=request_mac,150                one_rr_per_rrset=one_rr_per_rrset,151                ignore_trailing=ignore_trailing,152                raise_on_truncation=raise_on_truncation,153            )154        except dns.message.Truncated as e:155            # See the comment in query.py for details.156            if (157                ignore_errors158                and query is not None159                and not query.is_response(e.message())160            ):161                continue162            else:163                raise164        except Exception:165            if ignore_errors:166                continue167            else:168                raise169        if ignore_errors and query is not None and not query.is_response(r):170            continue171        return (r, received_time, from_address)172 173 174async def udp(175    q: dns.message.Message,176    where: str,177    timeout: Optional[float] = None,178    port: int = 53,179    source: Optional[str] = None,180    source_port: int = 0,181    ignore_unexpected: bool = False,182    one_rr_per_rrset: bool = False,183    ignore_trailing: bool = False,184    raise_on_truncation: bool = False,185    sock: Optional[dns.asyncbackend.DatagramSocket] = None,186    backend: Optional[dns.asyncbackend.Backend] = None,187    ignore_errors: bool = False,188) -> dns.message.Message:189    """Return the response obtained after sending a query via UDP.190 191    *sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``,192    the socket to use for the query.  If ``None``, the default, a193    socket is created.  Note that if a socket is provided, the194    *source*, *source_port*, and *backend* are ignored.195 196    *backend*, a ``dns.asyncbackend.Backend``, or ``None``.  If ``None``,197    the default, then dnspython will use the default backend.198 199    See :py:func:`dns.query.udp()` for the documentation of the other200    parameters, exceptions, and return type of this method.201    """202    wire = q.to_wire()203    (begin_time, expiration) = _compute_times(timeout)204    af = dns.inet.af_for_address(where)205    destination = _lltuple((where, port), af)206    if sock:207        cm: contextlib.AbstractAsyncContextManager = NullContext(sock)208    else:209        if not backend:210            backend = dns.asyncbackend.get_default_backend()211        stuple = _source_tuple(af, source, source_port)212        if backend.datagram_connection_required():213            dtuple = (where, port)214        else:215            dtuple = None216        cm = await backend.make_socket(af, socket.SOCK_DGRAM, 0, stuple, dtuple)217    async with cm as s:218        await send_udp(s, wire, destination, expiration)219        (r, received_time, _) = await receive_udp(220            s,221            destination,222            expiration,223            ignore_unexpected,224            one_rr_per_rrset,225            q.keyring,226            q.mac,227            ignore_trailing,228            raise_on_truncation,229            ignore_errors,230            q,231        )232        r.time = received_time - begin_time233        # We don't need to check q.is_response() if we are in ignore_errors mode234        # as receive_udp() will have checked it.235        if not (ignore_errors or q.is_response(r)):236            raise BadResponse237        return r238 239 240async def udp_with_fallback(241    q: dns.message.Message,242    where: str,243    timeout: Optional[float] = None,244    port: int = 53,245    source: Optional[str] = None,246    source_port: int = 0,247    ignore_unexpected: bool = False,248    one_rr_per_rrset: bool = False,249    ignore_trailing: bool = False,250    udp_sock: Optional[dns.asyncbackend.DatagramSocket] = None,251    tcp_sock: Optional[dns.asyncbackend.StreamSocket] = None,252    backend: Optional[dns.asyncbackend.Backend] = None,253    ignore_errors: bool = False,254) -> Tuple[dns.message.Message, bool]:255    """Return the response to the query, trying UDP first and falling back256    to TCP if UDP results in a truncated response.257 258    *udp_sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``,259    the socket to use for the UDP query.  If ``None``, the default, a260    socket is created.  Note that if a socket is provided the *source*,261    *source_port*, and *backend* are ignored for the UDP query.262 263    *tcp_sock*, a ``dns.asyncbackend.StreamSocket``, or ``None``, the264    socket to use for the TCP query.  If ``None``, the default, a265    socket is created.  Note that if a socket is provided *where*,266    *source*, *source_port*, and *backend*  are ignored for the TCP query.267 268    *backend*, a ``dns.asyncbackend.Backend``, or ``None``.  If ``None``,269    the default, then dnspython will use the default backend.270 271    See :py:func:`dns.query.udp_with_fallback()` for the documentation272    of the other parameters, exceptions, and return type of this273    method.274    """275    try:276        response = await udp(277            q,278            where,279            timeout,280            port,281            source,282            source_port,283            ignore_unexpected,284            one_rr_per_rrset,285            ignore_trailing,286            True,287            udp_sock,288            backend,289            ignore_errors,290        )291        return (response, False)292    except dns.message.Truncated:293        response = await tcp(294            q,295            where,296            timeout,297            port,298            source,299            source_port,300            one_rr_per_rrset,301            ignore_trailing,302            tcp_sock,303            backend,304        )305        return (response, True)306 307 308async def send_tcp(309    sock: dns.asyncbackend.StreamSocket,310    what: Union[dns.message.Message, bytes],311    expiration: Optional[float] = None,312) -> Tuple[int, float]:313    """Send a DNS message to the specified TCP socket.314 315    *sock*, a ``dns.asyncbackend.StreamSocket``.316 317    See :py:func:`dns.query.send_tcp()` for the documentation of the other318    parameters, exceptions, and return type of this method.319    """320 321    if isinstance(what, dns.message.Message):322        tcpmsg = what.to_wire(prepend_length=True)323    else:324        # copying the wire into tcpmsg is inefficient, but lets us325        # avoid writev() or doing a short write that would get pushed326        # onto the net327        tcpmsg = len(what).to_bytes(2, "big") + what328    sent_time = time.time()329    await sock.sendall(tcpmsg, _timeout(expiration, sent_time))330    return (len(tcpmsg), sent_time)331 332 333async def _read_exactly(sock, count, expiration):334    """Read the specified number of bytes from stream.  Keep trying until we335    either get the desired amount, or we hit EOF.336    """337    s = b""338    while count > 0:339        n = await sock.recv(count, _timeout(expiration))340        if n == b"":341            raise EOFError342        count = count - len(n)343        s = s + n344    return s345 346 347async def receive_tcp(348    sock: dns.asyncbackend.StreamSocket,349    expiration: Optional[float] = None,350    one_rr_per_rrset: bool = False,351    keyring: Optional[Dict[dns.name.Name, dns.tsig.Key]] = None,352    request_mac: Optional[bytes] = b"",353    ignore_trailing: bool = False,354) -> Tuple[dns.message.Message, float]:355    """Read a DNS message from a TCP socket.356 357    *sock*, a ``dns.asyncbackend.StreamSocket``.358 359    See :py:func:`dns.query.receive_tcp()` for the documentation of the other360    parameters, exceptions, and return type of this method.361    """362 363    ldata = await _read_exactly(sock, 2, expiration)364    (l,) = struct.unpack("!H", ldata)365    wire = await _read_exactly(sock, l, expiration)366    received_time = time.time()367    r = dns.message.from_wire(368        wire,369        keyring=keyring,370        request_mac=request_mac,371        one_rr_per_rrset=one_rr_per_rrset,372        ignore_trailing=ignore_trailing,373    )374    return (r, received_time)375 376 377async def tcp(378    q: dns.message.Message,379    where: str,380    timeout: Optional[float] = None,381    port: int = 53,382    source: Optional[str] = None,383    source_port: int = 0,384    one_rr_per_rrset: bool = False,385    ignore_trailing: bool = False,386    sock: Optional[dns.asyncbackend.StreamSocket] = None,387    backend: Optional[dns.asyncbackend.Backend] = None,388) -> dns.message.Message:389    """Return the response obtained after sending a query via TCP.390 391    *sock*, a ``dns.asyncbacket.StreamSocket``, or ``None``, the392    socket to use for the query.  If ``None``, the default, a socket393    is created.  Note that if a socket is provided394    *where*, *port*, *source*, *source_port*, and *backend* are ignored.395 396    *backend*, a ``dns.asyncbackend.Backend``, or ``None``.  If ``None``,397    the default, then dnspython will use the default backend.398 399    See :py:func:`dns.query.tcp()` for the documentation of the other400    parameters, exceptions, and return type of this method.401    """402 403    wire = q.to_wire()404    (begin_time, expiration) = _compute_times(timeout)405    if sock:406        # Verify that the socket is connected, as if it's not connected,407        # it's not writable, and the polling in send_tcp() will time out or408        # hang forever.409        await sock.getpeername()410        cm: contextlib.AbstractAsyncContextManager = NullContext(sock)411    else:412        # These are simple (address, port) pairs, not family-dependent tuples413        # you pass to low-level socket code.414        af = dns.inet.af_for_address(where)415        stuple = _source_tuple(af, source, source_port)416        dtuple = (where, port)417        if not backend:418            backend = dns.asyncbackend.get_default_backend()419        cm = await backend.make_socket(420            af, socket.SOCK_STREAM, 0, stuple, dtuple, timeout421        )422    async with cm as s:423        await send_tcp(s, wire, expiration)424        (r, received_time) = await receive_tcp(425            s, expiration, one_rr_per_rrset, q.keyring, q.mac, ignore_trailing426        )427        r.time = received_time - begin_time428        if not q.is_response(r):429            raise BadResponse430        return r431 432 433async def tls(434    q: dns.message.Message,435    where: str,436    timeout: Optional[float] = None,437    port: int = 853,438    source: Optional[str] = None,439    source_port: int = 0,440    one_rr_per_rrset: bool = False,441    ignore_trailing: bool = False,442    sock: Optional[dns.asyncbackend.StreamSocket] = None,443    backend: Optional[dns.asyncbackend.Backend] = None,444    ssl_context: Optional[ssl.SSLContext] = None,445    server_hostname: Optional[str] = None,446    verify: Union[bool, str] = True,447) -> dns.message.Message:448    """Return the response obtained after sending a query via TLS.449 450    *sock*, an ``asyncbackend.StreamSocket``, or ``None``, the socket451    to use for the query.  If ``None``, the default, a socket is452    created.  Note that if a socket is provided, it must be a453    connected SSL stream socket, and *where*, *port*,454    *source*, *source_port*, *backend*, *ssl_context*, and *server_hostname*455    are ignored.456 457    *backend*, a ``dns.asyncbackend.Backend``, or ``None``.  If ``None``,458    the default, then dnspython will use the default backend.459 460    See :py:func:`dns.query.tls()` for the documentation of the other461    parameters, exceptions, and return type of this method.462    """463    (begin_time, expiration) = _compute_times(timeout)464    if sock:465        cm: contextlib.AbstractAsyncContextManager = NullContext(sock)466    else:467        if ssl_context is None:468            ssl_context = _make_dot_ssl_context(server_hostname, verify)469        af = dns.inet.af_for_address(where)470        stuple = _source_tuple(af, source, source_port)471        dtuple = (where, port)472        if not backend:473            backend = dns.asyncbackend.get_default_backend()474        cm = await backend.make_socket(475            af,476            socket.SOCK_STREAM,477            0,478            stuple,479            dtuple,480            timeout,481            ssl_context,482            server_hostname,483        )484    async with cm as s:485        timeout = _timeout(expiration)486        response = await tcp(487            q,488            where,489            timeout,490            port,491            source,492            source_port,493            one_rr_per_rrset,494            ignore_trailing,495            s,496            backend,497        )498        end_time = time.time()499        response.time = end_time - begin_time500        return response501 502 503async def https(504    q: dns.message.Message,505    where: str,506    timeout: Optional[float] = None,507    port: int = 443,508    source: Optional[str] = None,509    source_port: int = 0,  # pylint: disable=W0613510    one_rr_per_rrset: bool = False,511    ignore_trailing: bool = False,512    client: Optional["httpx.AsyncClient"] = None,513    path: str = "/dns-query",514    post: bool = True,515    verify: Union[bool, str] = True,516    bootstrap_address: Optional[str] = None,517    resolver: Optional["dns.asyncresolver.Resolver"] = None,518    family: Optional[int] = socket.AF_UNSPEC,519) -> dns.message.Message:520    """Return the response obtained after sending a query via DNS-over-HTTPS.521 522    *client*, a ``httpx.AsyncClient``.  If provided, the client to use for523    the query.524 525    Unlike the other dnspython async functions, a backend cannot be provided526    in this function because httpx always auto-detects the async backend.527 528    See :py:func:`dns.query.https()` for the documentation of the other529    parameters, exceptions, and return type of this method.530    """531 532    if not have_doh:533        raise NoDOH  # pragma: no cover534    if client and not isinstance(client, httpx.AsyncClient):535        raise ValueError("session parameter must be an httpx.AsyncClient")536 537    wire = q.to_wire()538    try:539        af = dns.inet.af_for_address(where)540    except ValueError:541        af = None542    transport = None543    headers = {"accept": "application/dns-message"}544    if af is not None and dns.inet.is_address(where):545        if af == socket.AF_INET:546            url = "https://{}:{}{}".format(where, port, path)547        elif af == socket.AF_INET6:548            url = "https://[{}]:{}{}".format(where, port, path)549    else:550        url = where551 552    backend = dns.asyncbackend.get_default_backend()553 554    if source is None:555        local_address = None556        local_port = 0557    else:558        local_address = source559        local_port = source_port560    transport = backend.get_transport_class()(561        local_address=local_address,562        http1=True,563        http2=True,564        verify=verify,565        local_port=local_port,566        bootstrap_address=bootstrap_address,567        resolver=resolver,568        family=family,569    )570 571    if client:572        cm: contextlib.AbstractAsyncContextManager = NullContext(client)573    else:574        cm = httpx.AsyncClient(575            http1=True, http2=True, verify=verify, transport=transport576        )577 578    async with cm as the_client:579        # see https://tools.ietf.org/html/rfc8484#section-4.1.1 for DoH580        # GET and POST examples581        if post:582            headers.update(583                {584                    "content-type": "application/dns-message",585                    "content-length": str(len(wire)),586                }587            )588            response = await backend.wait_for(589                the_client.post(url, headers=headers, content=wire), timeout590            )591        else:592            wire = base64.urlsafe_b64encode(wire).rstrip(b"=")593            twire = wire.decode()  # httpx does a repr() if we give it bytes594            response = await backend.wait_for(595                the_client.get(url, headers=headers, params={"dns": twire}), timeout596            )597 598    # see https://tools.ietf.org/html/rfc8484#section-4.2.1 for info about DoH599    # status codes600    if response.status_code < 200 or response.status_code > 299:601        raise ValueError(602            "{} responded with status code {}"603            "\nResponse body: {!r}".format(604                where, response.status_code, response.content605            )606        )607    r = dns.message.from_wire(608        response.content,609        keyring=q.keyring,610        request_mac=q.request_mac,611        one_rr_per_rrset=one_rr_per_rrset,612        ignore_trailing=ignore_trailing,613    )614    r.time = response.elapsed.total_seconds()615    if not q.is_response(r):616        raise BadResponse617    return r618 619 620async def inbound_xfr(621    where: str,622    txn_manager: dns.transaction.TransactionManager,623    query: Optional[dns.message.Message] = None,624    port: int = 53,625    timeout: Optional[float] = None,626    lifetime: Optional[float] = None,627    source: Optional[str] = None,628    source_port: int = 0,629    udp_mode: UDPMode = UDPMode.NEVER,630    backend: Optional[dns.asyncbackend.Backend] = None,631) -> None:632    """Conduct an inbound transfer and apply it via a transaction from the633    txn_manager.634 635    *backend*, a ``dns.asyncbackend.Backend``, or ``None``.  If ``None``,636    the default, then dnspython will use the default backend.637 638    See :py:func:`dns.query.inbound_xfr()` for the documentation of639    the other parameters, exceptions, and return type of this method.640    """641    if query is None:642        (query, serial) = dns.xfr.make_query(txn_manager)643    else:644        serial = dns.xfr.extract_serial_from_query(query)645    rdtype = query.question[0].rdtype646    is_ixfr = rdtype == dns.rdatatype.IXFR647    origin = txn_manager.from_wire_origin()648    wire = query.to_wire()649    af = dns.inet.af_for_address(where)650    stuple = _source_tuple(af, source, source_port)651    dtuple = (where, port)652    (_, expiration) = _compute_times(lifetime)653    retry = True654    while retry:655        retry = False656        if is_ixfr and udp_mode != UDPMode.NEVER:657            sock_type = socket.SOCK_DGRAM658            is_udp = True659        else:660            sock_type = socket.SOCK_STREAM661            is_udp = False662        if not backend:663            backend = dns.asyncbackend.get_default_backend()664        s = await backend.make_socket(665            af, sock_type, 0, stuple, dtuple, _timeout(expiration)666        )667        async with s:668            if is_udp:669                await s.sendto(wire, dtuple, _timeout(expiration))670            else:671                tcpmsg = struct.pack("!H", len(wire)) + wire672                await s.sendall(tcpmsg, expiration)673            with dns.xfr.Inbound(txn_manager, rdtype, serial, is_udp) as inbound:674                done = False675                tsig_ctx = None676                while not done:677                    (_, mexpiration) = _compute_times(timeout)678                    if mexpiration is None or (679                        expiration is not None and mexpiration > expiration680                    ):681                        mexpiration = expiration682                    if is_udp:683                        destination = _lltuple((where, port), af)684                        while True:685                            timeout = _timeout(mexpiration)686                            (rwire, from_address) = await s.recvfrom(65535, timeout)687                            if _matches_destination(688                                af, from_address, destination, True689                            ):690                                break691                    else:692                        ldata = await _read_exactly(s, 2, mexpiration)693                        (l,) = struct.unpack("!H", ldata)694                        rwire = await _read_exactly(s, l, mexpiration)695                    is_ixfr = rdtype == dns.rdatatype.IXFR696                    r = dns.message.from_wire(697                        rwire,698                        keyring=query.keyring,699                        request_mac=query.mac,700                        xfr=True,701                        origin=origin,702                        tsig_ctx=tsig_ctx,703                        multi=(not is_udp),704                        one_rr_per_rrset=is_ixfr,705                    )706                    try:707                        done = inbound.process_message(r)708                    except dns.xfr.UseTCP:709                        assert is_udp  # should not happen if we used TCP!710                        if udp_mode == UDPMode.ONLY:711                            raise712                        done = True713                        retry = True714                        udp_mode = UDPMode.NEVER715                        continue716                    tsig_ctx = r.tsig_ctx717                if not retry and query.keyring and not r.had_tsig:718                    raise dns.exception.FormError("missing TSIG")719 720 721async def quic(722    q: dns.message.Message,723    where: str,724    timeout: Optional[float] = None,725    port: int = 853,726    source: Optional[str] = None,727    source_port: int = 0,728    one_rr_per_rrset: bool = False,729    ignore_trailing: bool = False,730    connection: Optional[dns.quic.AsyncQuicConnection] = None,731    verify: Union[bool, str] = True,732    backend: Optional[dns.asyncbackend.Backend] = None,733    server_hostname: Optional[str] = None,734) -> dns.message.Message:735    """Return the response obtained after sending an asynchronous query via736    DNS-over-QUIC.737 738    *backend*, a ``dns.asyncbackend.Backend``, or ``None``.  If ``None``,739    the default, then dnspython will use the default backend.740 741    See :py:func:`dns.query.quic()` for the documentation of the other742    parameters, exceptions, and return type of this method.743    """744 745    if not dns.quic.have_quic:746        raise NoDOQ("DNS-over-QUIC is not available.")  # pragma: no cover747 748    q.id = 0749    wire = q.to_wire()750    the_connection: dns.quic.AsyncQuicConnection751    if connection:752        cfactory = dns.quic.null_factory753        mfactory = dns.quic.null_factory754        the_connection = connection755    else:756        (cfactory, mfactory) = dns.quic.factories_for_backend(backend)757 758    async with cfactory() as context:759        async with mfactory(760            context, verify_mode=verify, server_name=server_hostname761        ) as the_manager:762            if not connection:763                the_connection = the_manager.connect(where, port, source, source_port)764            (start, expiration) = _compute_times(timeout)765            stream = await the_connection.make_stream(timeout)766            async with stream:767                await stream.send(wire, True)768                wire = await stream.receive(_remaining(expiration))769            finish = time.time()770        r = dns.message.from_wire(771            wire,772            keyring=q.keyring,773            request_mac=q.request_mac,774            one_rr_per_rrset=one_rr_per_rrset,775            ignore_trailing=ignore_trailing,776        )777    r.time = max(finish - start, 0.0)778    if not q.is_response(r):779        raise BadResponse780    return r781 
codekingpro/portable-devtools · Team Ai