Skip to content

Commit e32bb5d

Browse files
authored
New ServiceBrowsers now request QU in the first outgoing when unspecified (#812)
1 parent 13c558c commit e32bb5d

2 files changed

Lines changed: 31 additions & 14 deletions

File tree

tests/services/test_browser.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -544,19 +544,23 @@ def on_service_state_change(zeroconf, service_type, state_change, name):
544544
zeroconf_browser.close()
545545

546546

547-
def test_asking_default_is_asking_qm_questions():
548-
"""Verify the service browser can ask QU questions."""
547+
def test_asking_default_is_asking_qm_questions_after_the_first_qu():
548+
"""Verify the service browser's first question is QU and subsequent ones are QM questions."""
549549
type_ = "_quservice._tcp.local."
550550
zeroconf_browser = Zeroconf(interfaces=['127.0.0.1'])
551551

552552
# we are going to patch the zeroconf send to check query transmission
553553
old_send = zeroconf_browser.async_send
554554

555555
first_outgoing = None
556+
second_outgoing = None
556557

557558
def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT):
558559
"""Sends an outgoing packet."""
559560
nonlocal first_outgoing
561+
nonlocal second_outgoing
562+
if first_outgoing is not None and second_outgoing is None:
563+
second_outgoing = out
560564
if first_outgoing is None:
561565
first_outgoing = out
562566
old_send(out, addr=addr, port=port)
@@ -567,10 +571,11 @@ def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT):
567571
def on_service_state_change(zeroconf, service_type, state_change, name):
568572
pass
569573

570-
browser = ServiceBrowser(zeroconf_browser, type_, [on_service_state_change])
571-
time.sleep(millis_to_seconds(_services_browser._FIRST_QUERY_DELAY_RANDOM_INTERVAL[1] + 5))
574+
browser = ServiceBrowser(zeroconf_browser, type_, [on_service_state_change], delay=5)
575+
time.sleep(millis_to_seconds(_services_browser._FIRST_QUERY_DELAY_RANDOM_INTERVAL[1] + 120 + 5))
572576
try:
573-
assert first_outgoing.questions[0].unicast == False
577+
assert first_outgoing.questions[0].unicast == True
578+
assert second_outgoing.questions[0].unicast == False
574579
finally:
575580
browser.cancel()
576581
zeroconf_browser.close()
@@ -1016,7 +1021,7 @@ async def test_generate_service_query_suppress_duplicate_questions():
10161021
aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])
10171022
zc = aiozc.zeroconf
10181023
now = current_time_millis()
1019-
name = "_hap._tcp.local."
1024+
name = "_suppresstest._tcp.local."
10201025
question = r.DNSQuestion(name, const._TYPE_PTR, const._CLASS_IN)
10211026
answer = r.DNSPointer(
10221027
name,
@@ -1048,7 +1053,7 @@ async def test_generate_service_query_suppress_duplicate_questions():
10481053
outs = _services_browser.generate_service_query(zc, now, [name], multicast=False)
10491054
assert outs
10501055

1051-
zc.question_history.async_expire(now + 1000)
1056+
zc.question_history.async_expire(now + 2000)
10521057
# No suppression after clearing the history
10531058
outs = _services_browser.generate_service_query(zc, now, [name], multicast=True)
10541059
assert outs

zeroconf/_services/browser.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,14 @@ def generate_service_query(
146146
for record in zc.cache.get_all_by_details(type_, _TYPE_PTR, _CLASS_IN)
147147
if not record.is_stale(now)
148148
)
149-
if multicast and zc.question_history.suppresses(question, now, cast(Set[DNSRecord], known_answers)):
149+
if not qu_question and zc.question_history.suppresses(
150+
question, now, cast(Set[DNSRecord], known_answers)
151+
):
150152
log.debug("Asking %s was suppressed by the question history", question)
151153
continue
152154
questions_with_known_answers[question] = known_answers
153-
zc.question_history.add_question_at_time(question, now, cast(Set[DNSRecord], known_answers))
155+
if not qu_question:
156+
zc.question_history.add_question_at_time(question, now, cast(Set[DNSRecord], known_answers))
154157

155158
return _group_ptr_queries_with_known_answers(now, multicast, questions_with_known_answers)
156159

@@ -379,7 +382,7 @@ def _async_cancel(self) -> None:
379382
self.done = True
380383
self.zc.async_remove_listener(self)
381384

382-
def generate_ready_queries(self) -> List[DNSOutgoing]:
385+
def _generate_ready_queries(self, first_request: bool) -> List[DNSOutgoing]:
383386
"""Generate the service browser query for any type that is due."""
384387
now = current_time_millis()
385388
if self._millis_to_wait(current_time_millis()):
@@ -395,7 +398,13 @@ def generate_ready_queries(self) -> List[DNSOutgoing]:
395398
self._next_time[type_] = now + self._delay[type_]
396399
self._delay[type_] = min(_BROWSER_BACKOFF_LIMIT * 1000, self._delay[type_] * 2)
397400

398-
return generate_service_query(self.zc, now, ready_types, self.multicast, self.question_type)
401+
# If they did not specify and this is the first request, ask QU questions
402+
# https://datatracker.ietf.org/doc/html/rfc6762#section-5.4 since we are
403+
# just starting up and we know our cache is likely empty. This ensures
404+
# the next outgoing will be sent with the known answers list.
405+
question_type = DNSQuestionType.QU if not self.question_type and first_request else self.question_type
406+
407+
return generate_service_query(self.zc, now, ready_types, self.multicast, question_type)
399408

400409
def _millis_to_wait(self, now: float) -> Optional[float]:
401410
"""Returns the number of milliseconds to wait for the next event."""
@@ -406,14 +415,17 @@ def _millis_to_wait(self, now: float) -> Optional[float]:
406415
async def async_browser_task(self) -> None:
407416
"""Run the browser task."""
408417
await self.zc.async_wait_for_start()
418+
first_request = True
409419
while True:
410420
timeout = self._millis_to_wait(current_time_millis())
411421
if timeout:
412422
await self.zc.async_wait(timeout)
413423

414-
outs = self.generate_ready_queries()
415-
for out in outs:
416-
self.zc.async_send(out, addr=self.addr, port=self.port)
424+
outs = self._generate_ready_queries(first_request)
425+
if outs:
426+
first_request = False
427+
for out in outs:
428+
self.zc.async_send(out, addr=self.addr, port=self.port)
417429

418430
async def _async_cancel_browser(self) -> None:
419431
"""Cancel the browser."""

0 commit comments

Comments
 (0)