26
26
from ipaddress import IPv4Address , IPv6Address , _BaseAddress
27
27
from typing import TYPE_CHECKING , Dict , List , Optional , Set , Union , cast
28
28
29
+ from .._cache import DNSCache
29
30
from .._dns import (
30
31
DNSAddress ,
31
32
DNSNsec ,
32
33
DNSPointer ,
34
+ DNSQuestion ,
33
35
DNSQuestionType ,
34
36
DNSRecord ,
35
37
DNSService ,
36
38
DNSText ,
37
39
)
38
40
from .._exceptions import BadTypeInNameException
41
+ from .._history import QuestionHistory
39
42
from .._logger import log
40
43
from .._protocol .outgoing import DNSOutgoing
41
44
from .._record_update import RecordUpdate
61
64
_CLASS_IN_UNIQUE ,
62
65
_DNS_HOST_TTL ,
63
66
_DNS_OTHER_TTL ,
67
+ _DUPLICATE_QUESTION_INTERVAL ,
64
68
_FLAGS_QR_QUERY ,
65
69
_LISTENER_TIME ,
66
70
_MDNS_PORT ,
89
93
bytes_ = bytes
90
94
float_ = float
91
95
int_ = int
96
+ str_ = str
92
97
93
- DNS_QUESTION_TYPE_QU = DNSQuestionType .QU
94
- DNS_QUESTION_TYPE_QM = DNSQuestionType .QM
98
+ QU_QUESTION = DNSQuestionType .QU
99
+ QM_QUESTION = DNSQuestionType .QM
95
100
101
+ randint = random .randint
96
102
97
103
if TYPE_CHECKING :
98
104
from .._core import Zeroconf
@@ -774,6 +780,12 @@ def request(
774
780
)
775
781
)
776
782
783
+ def _get_initial_delay (self ) -> float_ :
784
+ return _LISTENER_TIME
785
+
786
+ def _get_random_delay (self ) -> int_ :
787
+ return randint (* _AVOID_SYNC_DELAY_RANDOM_INTERVAL )
788
+
777
789
async def async_request (
778
790
self ,
779
791
zc : 'Zeroconf' ,
@@ -804,7 +816,7 @@ async def async_request(
804
816
assert zc .loop is not None
805
817
806
818
first_request = True
807
- delay = _LISTENER_TIME
819
+ delay = self . _get_initial_delay ()
808
820
next_ = now
809
821
last = now + timeout
810
822
try :
@@ -813,18 +825,25 @@ async def async_request(
813
825
if last <= now :
814
826
return False
815
827
if next_ <= now :
816
- out = self ._generate_request_query (
817
- zc ,
818
- now ,
819
- question_type or DNS_QUESTION_TYPE_QU if first_request else DNS_QUESTION_TYPE_QM ,
820
- )
828
+ this_question_type = question_type or QU_QUESTION if first_request else QM_QUESTION
829
+ out = self ._generate_request_query (zc , now , this_question_type )
821
830
first_request = False
822
- if not out .questions :
823
- return self ._load_from_cache (zc , now )
824
- zc .async_send (out , addr , port )
831
+ if out .questions :
832
+ # All questions may have been suppressed
833
+ # by the question history, so nothing to send,
834
+ # but keep waiting for answers in case another
835
+ # client on the network is asking the same
836
+ # question or they have not arrived yet.
837
+ zc .async_send (out , addr , port )
825
838
next_ = now + delay
826
- delay *= 2
827
- next_ += random .randint (* _AVOID_SYNC_DELAY_RANDOM_INTERVAL )
839
+ next_ += self ._get_random_delay ()
840
+ if this_question_type is QM_QUESTION and delay < _DUPLICATE_QUESTION_INTERVAL :
841
+ # If we just asked a QM question, we need to
842
+ # wait at least the duplicate question interval
843
+ # before asking another QM question otherwise
844
+ # its likely to be suppressed by the question
845
+ # history of the remote responder.
846
+ delay = _DUPLICATE_QUESTION_INTERVAL
828
847
829
848
await self .async_wait (min (next_ , last ) - now , zc .loop )
830
849
now = current_time_millis ()
@@ -833,21 +852,57 @@ async def async_request(
833
852
834
853
return True
835
854
855
+ def _add_question_with_known_answers (
856
+ self ,
857
+ out : DNSOutgoing ,
858
+ qu_question : bool ,
859
+ question_history : QuestionHistory ,
860
+ cache : DNSCache ,
861
+ now : float_ ,
862
+ name : str_ ,
863
+ type_ : int_ ,
864
+ class_ : int_ ,
865
+ skip_if_known_answers : bool ,
866
+ ) -> None :
867
+ """Add a question with known answers if its not suppressed."""
868
+ known_answers = {
869
+ answer for answer in cache .get_all_by_details (name , type_ , class_ ) if not answer .is_stale (now )
870
+ }
871
+ if skip_if_known_answers and known_answers :
872
+ return
873
+ question = DNSQuestion (name , type_ , class_ )
874
+ if qu_question :
875
+ question .unicast = True
876
+ elif question_history .suppresses (question , now , known_answers ):
877
+ return
878
+ else :
879
+ question_history .add_question_at_time (question , now , known_answers )
880
+ out .add_question (question )
881
+ for answer in known_answers :
882
+ out .add_answer_at_time (answer , now )
883
+
836
884
def _generate_request_query (
837
885
self , zc : 'Zeroconf' , now : float_ , question_type : DNSQuestionType
838
886
) -> DNSOutgoing :
839
887
"""Generate the request query."""
840
888
out = DNSOutgoing (_FLAGS_QR_QUERY )
841
889
name = self ._name
842
- server_or_name = self .server or name
890
+ server = self .server or name
843
891
cache = zc .cache
844
- out .add_question_or_one_cache (cache , now , name , _TYPE_SRV , _CLASS_IN )
845
- out .add_question_or_one_cache (cache , now , name , _TYPE_TXT , _CLASS_IN )
846
- out .add_question_or_all_cache (cache , now , server_or_name , _TYPE_A , _CLASS_IN )
847
- out .add_question_or_all_cache (cache , now , server_or_name , _TYPE_AAAA , _CLASS_IN )
848
- if question_type is DNS_QUESTION_TYPE_QU :
849
- for question in out .questions :
850
- question .unicast = True
892
+ history = zc .question_history
893
+ qu_question = question_type is QU_QUESTION
894
+ self ._add_question_with_known_answers (
895
+ out , qu_question , history , cache , now , name , _TYPE_SRV , _CLASS_IN , True
896
+ )
897
+ self ._add_question_with_known_answers (
898
+ out , qu_question , history , cache , now , name , _TYPE_TXT , _CLASS_IN , True
899
+ )
900
+ self ._add_question_with_known_answers (
901
+ out , qu_question , history , cache , now , server , _TYPE_A , _CLASS_IN , False
902
+ )
903
+ self ._add_question_with_known_answers (
904
+ out , qu_question , history , cache , now , server , _TYPE_AAAA , _CLASS_IN , False
905
+ )
851
906
return out
852
907
853
908
def __repr__ (self ) -> str :
0 commit comments