Skip to content

Commit 37bfaf2

Browse files
authored
feat: speed up answering questions (#1265)
1 parent eef99c7 commit 37bfaf2

6 files changed

Lines changed: 92 additions & 20 deletions

File tree

src/zeroconf/_handlers/query_handler.pxd

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ from .._cache cimport DNSCache
55
from .._dns cimport DNSPointer, DNSQuestion, DNSRecord, DNSRRSet
66
from .._history cimport QuestionHistory
77
from .._protocol.incoming cimport DNSIncoming
8+
from .._services.info cimport ServiceInfo
89
from .._services.registry cimport ServiceRegistry
910

1011

1112
cdef object TYPE_CHECKING, QuestionAnswers
1213
cdef cython.uint _ONE_SECOND, _TYPE_PTR, _TYPE_ANY, _TYPE_A, _TYPE_AAAA, _TYPE_SRV, _TYPE_TXT
1314
cdef str _SERVICE_TYPE_ENUMERATION_NAME
1415
cdef cython.set _RESPOND_IMMEDIATE_TYPES
16+
cdef cython.set _ADDRESS_RECORD_TYPES
17+
cdef object IPVersion
18+
cdef object _TYPE_PTR, _CLASS_IN, _DNS_OTHER_TTL
1519

1620
cdef class _QueryResponse:
1721

@@ -45,13 +49,16 @@ cdef class QueryHandler:
4549
cdef DNSCache cache
4650
cdef QuestionHistory question_history
4751

52+
@cython.locals(service=ServiceInfo)
4853
cdef _add_service_type_enumeration_query_answers(self, cython.dict answer_set, DNSRRSet known_answers)
4954

55+
@cython.locals(service=ServiceInfo)
5056
cdef _add_pointer_answers(self, str lower_name, cython.dict answer_set, DNSRRSet known_answers)
5157

58+
@cython.locals(service=ServiceInfo)
5259
cdef _add_address_answers(self, str lower_name, cython.dict answer_set, DNSRRSet known_answers, cython.uint type_)
5360

54-
@cython.locals(question_lower_name=str, type_=cython.uint)
61+
@cython.locals(question_lower_name=str, type_=cython.uint, service=ServiceInfo)
5562
cdef _answer_question(self, DNSQuestion question, DNSRRSet known_answers)
5663

5764
@cython.locals(

src/zeroconf/_handlers/query_handler.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .._history import QuestionHistory
2929
from .._protocol.incoming import DNSIncoming
3030
from .._services.registry import ServiceRegistry
31+
from .._utils.net import IPVersion
3132
from ..const import (
3233
_ADDRESS_RECORD_TYPES,
3334
_CLASS_IN,
@@ -180,13 +181,13 @@ def _add_pointer_answers(
180181
for service in self.registry.async_get_infos_type(lower_name):
181182
# Add recommended additional answers according to
182183
# https://tools.ietf.org/html/rfc6763#section-12.1.
183-
dns_pointer = service.dns_pointer()
184+
dns_pointer = service._dns_pointer(None)
184185
if known_answers.suppresses(dns_pointer):
185186
continue
186187
answer_set[dns_pointer] = {
187-
service.dns_service(),
188-
service.dns_text(),
189-
} | service.get_address_and_nsec_records()
188+
service._dns_service(None),
189+
service._dns_text(None),
190+
} | service._get_address_and_nsec_records(None)
190191

191192
def _add_address_answers(
192193
self,
@@ -200,7 +201,7 @@ def _add_address_answers(
200201
answers: List[DNSAddress] = []
201202
additionals: Set[DNSRecord] = set()
202203
seen_types: Set[int] = set()
203-
for dns_address in service.dns_addresses():
204+
for dns_address in service._dns_addresses(None, IPVersion.All):
204205
seen_types.add(dns_address.type)
205206
if dns_address.type != type_:
206207
additionals.add(dns_address)
@@ -210,12 +211,12 @@ def _add_address_answers(
210211
if answers:
211212
if missing_types:
212213
assert service.server is not None, "Service server must be set for NSEC record."
213-
additionals.add(service.dns_nsec(list(missing_types)))
214+
additionals.add(service._dns_nsec(list(missing_types), None))
214215
for answer in answers:
215216
answer_set[answer] = additionals
216217
elif type_ in missing_types:
217218
assert service.server is not None, "Service server must be set for NSEC record."
218-
answer_set[service.dns_nsec(list(missing_types))] = set()
219+
answer_set[service._dns_nsec(list(missing_types), None)] = set()
219220

220221
def _answer_question(
221222
self,
@@ -243,11 +244,11 @@ def _answer_question(
243244
if type_ in (_TYPE_SRV, _TYPE_ANY):
244245
# Add recommended additional answers according to
245246
# https://tools.ietf.org/html/rfc6763#section-12.2.
246-
dns_service = service.dns_service()
247+
dns_service = service._dns_service(None)
247248
if not known_answers.suppresses(dns_service):
248-
answer_set[dns_service] = service.get_address_and_nsec_records()
249+
answer_set[dns_service] = service._get_address_and_nsec_records(None)
249250
if type_ in (_TYPE_TXT, _TYPE_ANY):
250-
dns_text = service.dns_text()
251+
dns_text = service._dns_text(None)
251252
if not known_answers.suppresses(dns_text):
252253
answer_set[dns_text] = set()
253254

src/zeroconf/_services/info.pxd

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import cython
33

44
from .._cache cimport DNSCache
5-
from .._dns cimport DNSPointer, DNSRecord, DNSService, DNSText
5+
from .._dns cimport DNSNsec, DNSPointer, DNSRecord, DNSService, DNSText
66
from .._protocol.outgoing cimport DNSOutgoing
77
from .._updates cimport RecordUpdateListener
88
from .._utils.time cimport current_time_millis
@@ -27,6 +27,8 @@ cdef object DNS_QUESTION_TYPE_QM
2727
cdef object _IPVersion_All_value
2828
cdef object _IPVersion_V4Only_value
2929

30+
cdef cython.set _ADDRESS_RECORD_TYPES
31+
3032
cdef object TYPE_CHECKING
3133

3234
cdef class ServiceInfo(RecordUpdateListener):
@@ -85,3 +87,20 @@ cdef class ServiceInfo(RecordUpdateListener):
8587
cdef cython.list _ip_addresses_by_version_value(self, object version_value)
8688

8789
cdef addresses_by_version(self, object version)
90+
91+
@cython.locals(cacheable=cython.bint)
92+
cdef cython.list _dns_addresses(self, object override_ttls, object version)
93+
94+
@cython.locals(cacheable=cython.bint)
95+
cdef DNSPointer _dns_pointer(self, object override_ttl)
96+
97+
@cython.locals(cacheable=cython.bint)
98+
cdef DNSService _dns_service(self, object override_ttl)
99+
100+
@cython.locals(cacheable=cython.bint)
101+
cdef DNSText _dns_text(self, object override_ttl)
102+
103+
cdef DNSNsec _dns_nsec(self, cython.list missing_types, object override_ttl)
104+
105+
@cython.locals(cacheable=cython.bint)
106+
cdef cython.set _get_address_and_nsec_records(self, object override_ttl)

src/zeroconf/_services/info.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,14 @@ def dns_addresses(
519519
self,
520520
override_ttl: Optional[int] = None,
521521
version: IPVersion = IPVersion.All,
522+
) -> List[DNSAddress]:
523+
"""Return matching DNSAddress from ServiceInfo."""
524+
return self._dns_addresses(override_ttl, version)
525+
526+
def _dns_addresses(
527+
self,
528+
override_ttl: Optional[int],
529+
version: IPVersion,
522530
) -> List[DNSAddress]:
523531
"""Return matching DNSAddress from ServiceInfo."""
524532
cacheable = version is IPVersion.All and override_ttl is None
@@ -544,6 +552,10 @@ def dns_addresses(
544552
return records
545553

546554
def dns_pointer(self, override_ttl: Optional[int] = None) -> DNSPointer:
555+
"""Return DNSPointer from ServiceInfo."""
556+
return self._dns_pointer(override_ttl)
557+
558+
def _dns_pointer(self, override_ttl: Optional[int]) -> DNSPointer:
547559
"""Return DNSPointer from ServiceInfo."""
548560
cacheable = override_ttl is None
549561
if self._dns_pointer_cache is not None and cacheable:
@@ -561,6 +573,10 @@ def dns_pointer(self, override_ttl: Optional[int] = None) -> DNSPointer:
561573
return record
562574

563575
def dns_service(self, override_ttl: Optional[int] = None) -> DNSService:
576+
"""Return DNSService from ServiceInfo."""
577+
return self._dns_service(override_ttl)
578+
579+
def _dns_service(self, override_ttl: Optional[int]) -> DNSService:
564580
"""Return DNSService from ServiceInfo."""
565581
cacheable = override_ttl is None
566582
if self._dns_service_cache is not None and cacheable:
@@ -584,6 +600,10 @@ def dns_service(self, override_ttl: Optional[int] = None) -> DNSService:
584600
return record
585601

586602
def dns_text(self, override_ttl: Optional[int] = None) -> DNSText:
603+
"""Return DNSText from ServiceInfo."""
604+
return self._dns_text(override_ttl)
605+
606+
def _dns_text(self, override_ttl: Optional[int]) -> DNSText:
587607
"""Return DNSText from ServiceInfo."""
588608
cacheable = override_ttl is None
589609
if self._dns_text_cache is not None and cacheable:
@@ -601,6 +621,10 @@ def dns_text(self, override_ttl: Optional[int] = None) -> DNSText:
601621
return record
602622

603623
def dns_nsec(self, missing_types: List[int], override_ttl: Optional[int] = None) -> DNSNsec:
624+
"""Return DNSNsec from ServiceInfo."""
625+
return self._dns_nsec(missing_types, override_ttl)
626+
627+
def _dns_nsec(self, missing_types: List[int], override_ttl: Optional[int]) -> DNSNsec:
604628
"""Return DNSNsec from ServiceInfo."""
605629
return DNSNsec(
606630
self._name,
@@ -613,18 +637,22 @@ def dns_nsec(self, missing_types: List[int], override_ttl: Optional[int] = None)
613637
)
614638

615639
def get_address_and_nsec_records(self, override_ttl: Optional[int] = None) -> Set[DNSRecord]:
640+
"""Build a set of address records and NSEC records for non-present record types."""
641+
return self._get_address_and_nsec_records(override_ttl)
642+
643+
def _get_address_and_nsec_records(self, override_ttl: Optional[int]) -> Set[DNSRecord]:
616644
"""Build a set of address records and NSEC records for non-present record types."""
617645
cacheable = override_ttl is None
618646
if self._get_address_and_nsec_records_cache is not None and cacheable:
619647
return self._get_address_and_nsec_records_cache
620648
missing_types: Set[int] = _ADDRESS_RECORD_TYPES.copy()
621649
records: Set[DNSRecord] = set()
622-
for dns_address in self.dns_addresses(override_ttl, IPVersion.All):
650+
for dns_address in self._dns_addresses(override_ttl, IPVersion.All):
623651
missing_types.discard(dns_address.type)
624652
records.add(dns_address)
625653
if missing_types:
626654
assert self.server is not None, "Service server must be set for NSEC record."
627-
records.add(self.dns_nsec(list(missing_types), override_ttl))
655+
records.add(self._dns_nsec(list(missing_types), override_ttl))
628656
if cacheable:
629657
self._get_address_and_nsec_records_cache = records
630658
return records
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
import cython
33

4+
from .info cimport ServiceInfo
5+
46

57
cdef class ServiceRegistry:
68

@@ -11,16 +13,16 @@ cdef class ServiceRegistry:
1113
@cython.locals(
1214
record_list=cython.list,
1315
)
14-
cdef _async_get_by_index(self, cython.dict records, str key)
16+
cdef cython.list _async_get_by_index(self, cython.dict records, str key)
1517

16-
cdef _add(self, object info)
18+
cdef _add(self, ServiceInfo info)
1719

1820
cdef _remove(self, cython.list infos)
1921

20-
cpdef async_get_info_name(self, str name)
22+
cpdef ServiceInfo async_get_info_name(self, str name)
2123

22-
cpdef async_get_types(self)
24+
cpdef cython.list async_get_types(self)
2325

24-
cpdef async_get_infos_type(self, str type_)
26+
cpdef cython.list async_get_infos_type(self, str type_)
2527

26-
cpdef async_get_infos_server(self, str server)
28+
cpdef cython.list async_get_infos_server(self, str server)

tests/services/test_info.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,3 +1535,18 @@ async def test_release_wait_when_new_recorded_added_concurrency():
15351535
assert not pending
15361536
assert info.addresses == [b'\x7f\x00\x00\x01']
15371537
await aiozc.async_close()
1538+
1539+
1540+
@pytest.mark.asyncio
1541+
async def test_service_info_nsec_records():
1542+
"""Test we can generate nsec records from ServiceInfo."""
1543+
type_ = "_http._tcp.local."
1544+
registration_name = "multiareccon.%s" % type_
1545+
desc = {'path': '/~paulsm/'}
1546+
host = "multahostcon.local."
1547+
info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, host)
1548+
nsec_record = info.dns_nsec([const._TYPE_A, const._TYPE_AAAA], 50)
1549+
assert nsec_record.name == registration_name
1550+
assert nsec_record.type == const._TYPE_NSEC
1551+
assert nsec_record.ttl == 50
1552+
assert nsec_record.rdtypes == [const._TYPE_A, const._TYPE_AAAA]

0 commit comments

Comments
 (0)