diff --git a/dns/asyncquery.py b/dns/asyncquery.py index 7e9b886a..43c42caf 100644 --- a/dns/asyncquery.py +++ b/dns/asyncquery.py @@ -732,7 +732,7 @@ async def _http3( if url_parts.port is not None: port = url_parts.port - q.id = 0 + q.set_id(0) wire = q.to_wire() the_connection: dns.quic.AsyncQuicConnection if connection: @@ -804,7 +804,7 @@ async def quic( if server_hostname is not None and hostname is None: hostname = server_hostname - q.id = 0 + q.set_id(0) wire = q.to_wire() the_connection: dns.quic.AsyncQuicConnection if connection: diff --git a/dns/asyncresolver.py b/dns/asyncresolver.py index c18ad0b1..2b8f6d97 100644 --- a/dns/asyncresolver.py +++ b/dns/asyncresolver.py @@ -89,6 +89,7 @@ async def resolve( nameserver, tcp, backoff = resolution.next_nameserver() if backoff: await backend.sleep(backoff) + request.set_random_id() timeout = self._compute_timeout(start, lifetime, resolution.errors) try: response = await nameserver.async_query( diff --git a/dns/message.py b/dns/message.py index 1cacb786..d514e56c 100644 --- a/dns/message.py +++ b/dns/message.py @@ -801,6 +801,25 @@ def had_tsig(self) -> bool: def _make_opt(flags=0, payload=DEFAULT_EDNS_PAYLOAD, options=None): return dns.renderer._make_opt(flags, payload, options) + def set_id(self, id: int): + """Set the message id to the specified value, and if TSIG is going to be used + to sign this message later, update it accordingly. + + :param id: The message id + :type id: int + """ + self.id = id + if self.want_tsig_sign: + assert self.tsig is not None + assert self.tsig.rdtype == dns.rdatatype.TSIG + tsig_rdata = cast(dns.rdtypes.ANY.TSIG.TSIG, self.tsig[0]) + self.use_tsig(self.keyring, None, tsig_rdata.fudge, id, tsig_rdata.error, tsig_rdata.other, + tsig_rdata.algorithm) + + def set_random_id(self): + """Assign a newly-generated random query id to the message.""" + self.set_id(dns.entropy.random_16()) + def use_edns( self, edns: int | bool | None = 0, diff --git a/dns/query.py b/dns/query.py index 5757d209..f668f493 100644 --- a/dns/query.py +++ b/dns/query.py @@ -681,7 +681,7 @@ def _http3( if url_parts.port is not None: port = url_parts.port - q.id = 0 + q.set_id(0) wire = q.to_wire() the_connection: dns.quic.SyncQuicConnection the_manager: dns.quic.SyncQuicManager @@ -1444,7 +1444,7 @@ def quic( if server_hostname is not None and hostname is None: hostname = server_hostname - q.id = 0 + q.set_id(0) wire = q.to_wire() the_connection: dns.quic.SyncQuicConnection the_manager: dns.quic.SyncQuicManager diff --git a/dns/resolver.py b/dns/resolver.py index 6ded05ce..403c4222 100644 --- a/dns/resolver.py +++ b/dns/resolver.py @@ -32,7 +32,6 @@ import dns._file_util import dns.edns import dns.exception -import dns.flags import dns.inet import dns.ipv4 import dns.ipv6 @@ -41,11 +40,9 @@ import dns.nameserver import dns.query import dns.rcode -import dns.rdata import dns.rdataclass import dns.rdatatype import dns.rdtypes.ANY.PTR -import dns.rdtypes.svcbbase import dns.reversename import dns.tsig @@ -1317,6 +1314,7 @@ def resolve( nameserver, tcp, backoff = resolution.next_nameserver() if backoff: time.sleep(backoff) + request.set_random_id() timeout = self._compute_timeout(start, lifetime, resolution.errors) try: response = nameserver.query( diff --git a/doc/whatsnew.rst b/doc/whatsnew.rst index b7aa190e..dd61953a 100644 --- a/doc/whatsnew.rst +++ b/doc/whatsnew.rst @@ -3,7 +3,7 @@ What's New in dnspython ======================= -2.9.10 (in development) +2.10.0 (in development) * The type annotation of ``public_key()`` on the ``dns.dnssecalgs`` private key classes is now ``dns.dnssecalgs.base.GenericPublicKey``, as declared by @@ -18,6 +18,11 @@ What's New in dnspython 2.9.1 (in development) ---------------------- +* The resolver now assigns a new random query id to every query. + +* For transports that require a 0 query id, set it in a way that does not break + TSIG. + * ``dns.message.QueryMessage.resolve_chaining()`` now caps the minimum TTL of a negative response with no SOA record at 300 seconds (``dns.message.NEGATIVE_TTL_WITHOUT_SOA``). Previously the TTL was left at @@ -147,7 +152,7 @@ What's New in dnspython signed response failed with ``BadSignature`` and a response with no TSIG at all was accepted, as the RFC 8945 section 5.4 check for a missing TSIG was skipped. - + * ``dns.query.inbound_xfr()``, ``dns.asyncquery.inbound_xfr()``, and ``dns.query.xfr()`` now check every response message with ``dns.message.Message.is_response()`` and raise ``dns.query.BadResponse`` if