Skip to content

Commit

Permalink
feat: small speed up to processing incoming dns records (#1315)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed Nov 14, 2023
1 parent bf2cfde commit bfe4c24
Show file tree
Hide file tree
Showing 15 changed files with 136 additions and 102 deletions.
2 changes: 1 addition & 1 deletion src/zeroconf/_cache.pxd
Expand Up @@ -24,7 +24,7 @@ cdef class DNSCache:
cdef public cython.dict cache
cdef public cython.dict service_cache

cpdef async_add_records(self, object entries)
cpdef bint async_add_records(self, object entries)

cpdef async_remove_records(self, object entries)

Expand Down
62 changes: 34 additions & 28 deletions src/zeroconf/_dns.pxd
Expand Up @@ -4,19 +4,21 @@ import cython
from ._protocol.outgoing cimport DNSOutgoing


cdef object _LEN_BYTE
cdef object _LEN_SHORT
cdef object _LEN_INT
cdef cython.uint _LEN_BYTE
cdef cython.uint _LEN_SHORT
cdef cython.uint _LEN_INT

cdef object _NAME_COMPRESSION_MIN_SIZE
cdef object _BASE_MAX_SIZE
cdef cython.uint _NAME_COMPRESSION_MIN_SIZE
cdef cython.uint _BASE_MAX_SIZE

cdef cython.uint _EXPIRE_FULL_TIME_MS
cdef cython.uint _EXPIRE_STALE_TIME_MS
cdef cython.uint _RECENT_TIME_MS

cdef object _CLASS_UNIQUE
cdef object _CLASS_MASK
cdef cython.uint _TYPE_ANY

cdef cython.uint _CLASS_UNIQUE
cdef cython.uint _CLASS_MASK

cdef object current_time_millis

Expand All @@ -25,36 +27,40 @@ cdef class DNSEntry:
cdef public str key
cdef public str name
cdef public cython.uint type
cdef public object class_
cdef public object unique
cdef public cython.uint class_
cdef public bint unique

cdef _set_class(self, cython.uint class_)

cdef _dns_entry_matches(self, DNSEntry other)
cdef bint _dns_entry_matches(self, DNSEntry other)

cdef class DNSQuestion(DNSEntry):

cdef public cython.int _hash

cpdef bint answered_by(self, DNSRecord rec)

cdef class DNSRecord(DNSEntry):

cdef public cython.float ttl
cdef public cython.float created

cdef _suppressed_by_answer(self, DNSRecord answer)
cdef bint _suppressed_by_answer(self, DNSRecord answer)

@cython.locals(
answers=cython.list,
)
cpdef suppressed_by(self, object msg)
cpdef bint suppressed_by(self, object msg)

cpdef get_remaining_ttl(self, cython.float now)

cpdef get_expiration_time(self, cython.uint percent)

cpdef is_expired(self, cython.float now)
cpdef bint is_expired(self, cython.float now)

cpdef is_stale(self, cython.float now)
cpdef bint is_stale(self, cython.float now)

cpdef is_recent(self, cython.float now)
cpdef bint is_recent(self, cython.float now)

cpdef reset_ttl(self, DNSRecord other)

Expand All @@ -66,18 +72,18 @@ cdef class DNSAddress(DNSRecord):
cdef public object address
cdef public object scope_id

cdef _eq(self, DNSAddress other)
cdef bint _eq(self, DNSAddress other)

cpdef write(self, DNSOutgoing out)


cdef class DNSHinfo(DNSRecord):

cdef public cython.int _hash
cdef public object cpu
cdef public object os
cdef public str cpu
cdef public str os

cdef _eq(self, DNSHinfo other)
cdef bint _eq(self, DNSHinfo other)

cpdef write(self, DNSOutgoing out)

Expand All @@ -87,29 +93,29 @@ cdef class DNSPointer(DNSRecord):
cdef public str alias
cdef public str alias_key

cdef _eq(self, DNSPointer other)
cdef bint _eq(self, DNSPointer other)

cpdef write(self, DNSOutgoing out)

cdef class DNSText(DNSRecord):

cdef public cython.int _hash
cdef public object text
cdef public bytes text

cdef _eq(self, DNSText other)
cdef bint _eq(self, DNSText other)

cpdef write(self, DNSOutgoing out)

cdef class DNSService(DNSRecord):

cdef public cython.int _hash
cdef public object priority
cdef public object weight
cdef public object port
cdef public cython.uint priority
cdef public cython.uint weight
cdef public cython.uint port
cdef public str server
cdef public str server_key

cdef _eq(self, DNSService other)
cdef bint _eq(self, DNSService other)

cpdef write(self, DNSOutgoing out)

Expand All @@ -119,7 +125,7 @@ cdef class DNSNsec(DNSRecord):
cdef public object next_name
cdef public cython.list rdtypes

cdef _eq(self, DNSNsec other)
cdef bint _eq(self, DNSNsec other)

cpdef write(self, DNSOutgoing out)

Expand All @@ -129,7 +135,7 @@ cdef class DNSRRSet:
cdef cython.dict _lookup

@cython.locals(other=DNSRecord)
cpdef suppresses(self, DNSRecord record)
cpdef bint suppresses(self, DNSRecord record)

@cython.locals(
record=DNSRecord,
Expand Down
6 changes: 4 additions & 2 deletions src/zeroconf/_dns.py
Expand Up @@ -67,10 +67,13 @@ class DNSEntry:

__slots__ = ('key', 'name', 'type', 'class_', 'unique')

def __init__(self, name: str, type_: _int, class_: _int) -> None:
def __init__(self, name: str, type_: int, class_: int) -> None:
self.name = name
self.key = name.lower()
self.type = type_
self._set_class(class_)

def _set_class(self, class_: _int) -> None:
self.class_ = class_ & _CLASS_MASK
self.unique = (class_ & _CLASS_UNIQUE) != 0

Expand Down Expand Up @@ -371,7 +374,6 @@ class DNSText(DNSRecord):
def __init__(
self, name: str, type_: int, class_: int, ttl: int, text: bytes, created: Optional[float] = None
) -> None:
assert isinstance(text, (bytes, type(None)))
super().__init__(name, type_, class_, ttl, created)
self.text = text
self._hash = hash((self.key, type_, self.class_, text))
Expand Down
16 changes: 8 additions & 8 deletions src/zeroconf/_handlers/query_handler.py
Expand Up @@ -167,7 +167,7 @@ def _has_mcast_within_one_quarter_ttl(self, record: DNSRecord) -> bool:
if TYPE_CHECKING:
record = cast(_UniqueRecordsType, record)
maybe_entry = self._cache.async_get_unique(record)
return bool(maybe_entry is not None and maybe_entry.is_recent(self._now) is True)
return bool(maybe_entry is not None and maybe_entry.is_recent(self._now))

def _has_mcast_record_in_last_second(self, record: DNSRecord) -> bool:
"""Check if an answer was seen in the last second.
Expand Down Expand Up @@ -202,7 +202,7 @@ def _add_service_type_enumeration_query_answers(
dns_pointer = DNSPointer(
_SERVICE_TYPE_ENUMERATION_NAME, _TYPE_PTR, _CLASS_IN, _DNS_OTHER_TTL, stype, 0.0
)
if known_answers.suppresses(dns_pointer) is False:
if not known_answers.suppresses(dns_pointer):
answer_set[dns_pointer] = set()

def _add_pointer_answers(
Expand All @@ -213,7 +213,7 @@ def _add_pointer_answers(
# Add recommended additional answers according to
# https://tools.ietf.org/html/rfc6763#section-12.1.
dns_pointer = service._dns_pointer(None)
if known_answers.suppresses(dns_pointer) is True:
if known_answers.suppresses(dns_pointer):
continue
answer_set[dns_pointer] = {
service._dns_service(None),
Expand All @@ -237,7 +237,7 @@ def _add_address_answers(
seen_types.add(dns_address.type)
if dns_address.type != type_:
additionals.add(dns_address)
elif known_answers.suppresses(dns_address) is False:
elif not known_answers.suppresses(dns_address):
answers.append(dns_address)
missing_types: Set[int] = _ADDRESS_RECORD_TYPES - seen_types
if answers:
Expand Down Expand Up @@ -272,12 +272,12 @@ def _answer_question(
# https://tools.ietf.org/html/rfc6763#section-12.2.
service = services[0]
dns_service = service._dns_service(None)
if known_answers.suppresses(dns_service) is False:
if not known_answers.suppresses(dns_service):
answer_set[dns_service] = service._get_address_and_nsec_records(None)
elif strategy_type == _ANSWER_STRATEGY_TEXT: # pragma: no branch
service = services[0]
dns_text = service._dns_text(None)
if known_answers.suppresses(dns_text) is False:
if not known_answers.suppresses(dns_text):
answer_set[dns_text] = set()

return answer_set
Expand Down Expand Up @@ -307,7 +307,7 @@ def async_response( # pylint: disable=unused-argument
# at least one answer strategy
answers: List[DNSRecord] = []
for msg in msgs:
if msg.is_probe() is True:
if msg.is_probe():
is_probe = True
else:
answers.extend(msg.answers())
Expand All @@ -319,7 +319,7 @@ def async_response( # pylint: disable=unused-argument
now = msg.now
for strategy in strategies:
question = strategy.question
is_unicast = question.unique is True # unique and unicast are the same flag
is_unicast = question.unique # unique and unicast are the same flag
if not is_unicast:
if known_answers_set is None: # pragma: no branch
known_answers_set = known_answers.lookup_set()
Expand Down
6 changes: 3 additions & 3 deletions src/zeroconf/_handlers/record_manager.py
Expand Up @@ -106,14 +106,14 @@ def async_updates_from_response(self, msg: DNSIncoming) -> None:
)
record.set_created_ttl(record.created, _DNS_PTR_MIN_TTL)

if record.unique is True: # https://tools.ietf.org/html/rfc6762#section-10.2
if record.unique: # https://tools.ietf.org/html/rfc6762#section-10.2
unique_types.add((record.name, record_type, record.class_))

if TYPE_CHECKING:
record = cast(_UniqueRecordsType, record)

maybe_entry = cache.async_get_unique(record)
if record.is_expired(now_float) is False:
if not record.is_expired(now_float):
if maybe_entry is not None:
maybe_entry.reset_ttl(record)
else:
Expand Down Expand Up @@ -151,7 +151,7 @@ def async_updates_from_response(self, msg: DNSIncoming) -> None:
new = False
if other_adds or address_adds:
new = cache.async_add_records(address_adds)
if cache.async_add_records(other_adds) is True:
if cache.async_add_records(other_adds):
new = True
# Removes are processed last since
# ServiceInfo could generate an un-needed query
Expand Down
2 changes: 1 addition & 1 deletion src/zeroconf/_history.pxd
Expand Up @@ -12,7 +12,7 @@ cdef class QuestionHistory:
cpdef add_question_at_time(self, DNSQuestion question, float now, cython.set known_answers)

@cython.locals(than=cython.double, previous_question=cython.tuple, previous_known_answers=cython.set)
cpdef suppresses(self, DNSQuestion question, cython.double now, cython.set known_answers)
cpdef bint suppresses(self, DNSQuestion question, cython.double now, cython.set known_answers)

@cython.locals(than=cython.double, now_known_answers=cython.tuple)
cpdef async_expire(self, cython.double now)
4 changes: 2 additions & 2 deletions src/zeroconf/_listener.py
Expand Up @@ -113,7 +113,7 @@ def _process_datagram_at_time(
self.data == data
and (now - _DUPLICATE_PACKET_SUPPRESSION_INTERVAL) < self.last_time
and self.last_message is not None
and self.last_message.has_qu_question() is False
and not self.last_message.has_qu_question()
):
# Guard against duplicate packets
if debug:
Expand Down Expand Up @@ -169,7 +169,7 @@ def _process_datagram_at_time(
)
return

if msg.is_query() is False:
if not msg.is_query():
self._record_manager.async_updates_from_response(msg)
return

Expand Down
25 changes: 13 additions & 12 deletions src/zeroconf/_protocol/incoming.pxd
Expand Up @@ -52,32 +52,33 @@ cdef class DNSIncoming:
cdef public bytes data
cdef const unsigned char [:] view
cdef unsigned int _data_len
cdef public cython.dict name_cache
cdef public cython.list questions
cdef cython.dict _name_cache
cdef cython.list _questions
cdef cython.list _answers
cdef public object id
cdef public cython.uint num_questions
cdef public cython.uint num_answers
cdef public cython.uint num_authorities
cdef public cython.uint num_additionals
cdef public object valid
cdef public cython.uint id
cdef cython.uint _num_questions
cdef cython.uint _num_answers
cdef cython.uint _num_authorities
cdef cython.uint _num_additionals
cdef public bint valid
cdef public object now
cdef cython.float _now_float
cdef public object scope_id
cdef public object source
cdef bint _has_qu_question

@cython.locals(
question=DNSQuestion
)
cpdef has_qu_question(self)
cpdef bint has_qu_question(self)

cpdef is_query(self)
cpdef bint is_query(self)

cpdef is_probe(self)
cpdef bint is_probe(self)

cpdef answers(self)

cpdef is_response(self)
cpdef bint is_response(self)

@cython.locals(
off=cython.uint,
Expand Down

0 comments on commit bfe4c24

Please sign in to comment.