feat: speed up the service registry (#1174) · python-zeroconf/python-zeroconf@360ceb2

@@ -340,6 +340,32 @@ def test_aaaa_query():

340340

zc.close()

341341342342343+

@unittest.skipIf(not has_working_ipv6(), 'Requires IPv6')

344+

@unittest.skipIf(os.environ.get('SKIP_IPV6'), 'IPv6 tests disabled')

345+

def test_aaaa_query_upper_case():

346+

"""Test that queries for AAAA records work and should respond right away with an upper case name."""

347+

zc = Zeroconf(interfaces=['127.0.0.1'])

348+

type_ = "_knownaaaservice._tcp.local."

349+

name = "knownname"

350+

registration_name = f"{name}.{type_}"

351+

desc = {'path': '/~paulsm/'}

352+

server_name = "ash-2.local."

353+

ipv6_address = socket.inet_pton(socket.AF_INET6, "2001:db8::1")

354+

info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, server_name, addresses=[ipv6_address])

355+

zc.registry.async_add(info)

356+357+

generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)

358+

question = r.DNSQuestion(server_name.upper(), const._TYPE_AAAA, const._CLASS_IN)

359+

generated.add_question(question)

360+

packets = generated.packets()

361+

question_answers = zc.query_handler.async_response([r.DNSIncoming(packet) for packet in packets], False)

362+

mcast_answers = list(question_answers.mcast_now)

363+

assert mcast_answers[0].address == ipv6_address # type: ignore[attr-defined]

364+

# unregister

365+

zc.registry.async_remove(info)

366+

zc.close()

367+368+343369

@unittest.skipIf(not has_working_ipv6(), 'Requires IPv6')

344370

@unittest.skipIf(os.environ.get('SKIP_IPV6'), 'IPv6 tests disabled')

345371

def test_a_and_aaaa_record_fate_sharing():

@@ -481,6 +507,48 @@ async def test_probe_answered_immediately():

481507

zc.close()

482508483509510+

@pytest.mark.asyncio

511+

async def test_probe_answered_immediately_with_uppercase_name():

512+

"""Verify probes are responded to immediately with an uppercase name."""

513+

# instantiate a zeroconf instance

514+

zc = Zeroconf(interfaces=['127.0.0.1'])

515+516+

# service definition

517+

type_ = "_test-srvc-type._tcp.local."

518+

name = "xxxyyy"

519+

registration_name = f"{name}.{type_}"

520+

desc = {'path': '/~paulsm/'}

521+

info = ServiceInfo(

522+

type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")]

523+

)

524+

zc.registry.async_add(info)

525+

query = r.DNSOutgoing(const._FLAGS_QR_QUERY)

526+

question = r.DNSQuestion(info.type.upper(), const._TYPE_PTR, const._CLASS_IN)

527+

query.add_question(question)

528+

query.add_authorative_answer(info.dns_pointer())

529+

question_answers = zc.query_handler.async_response(

530+

[r.DNSIncoming(packet) for packet in query.packets()], False

531+

)

532+

assert not question_answers.ucast

533+

assert not question_answers.mcast_aggregate

534+

assert not question_answers.mcast_aggregate_last_second

535+

assert question_answers.mcast_now

536+537+

query = r.DNSOutgoing(const._FLAGS_QR_QUERY)

538+

question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN)

539+

question.unicast = True

540+

query.add_question(question)

541+

query.add_authorative_answer(info.dns_pointer())

542+

question_answers = zc.query_handler.async_response(

543+

[r.DNSIncoming(packet) for packet in query.packets()], False

544+

)

545+

assert question_answers.ucast

546+

assert question_answers.mcast_now

547+

assert not question_answers.mcast_aggregate

548+

assert not question_answers.mcast_aggregate_last_second

549+

zc.close()

550+551+484552

def test_qu_response():

485553

"""Handle multicast incoming with the QU bit set."""

486554

# instantiate a zeroconf instance

@@ -842,6 +910,45 @@ def test_known_answer_supression_service_type_enumeration_query():

842910

zc.close()

843911844912913+

def test_upper_case_enumeration_query():

914+

zc = Zeroconf(interfaces=['127.0.0.1'])

915+

type_ = "_otherknown._tcp.local."

916+

name = "knownname"

917+

registration_name = f"{name}.{type_}"

918+

desc = {'path': '/~paulsm/'}

919+

server_name = "ash-2.local."

920+

info = ServiceInfo(

921+

type_, registration_name, 80, 0, 0, desc, server_name, addresses=[socket.inet_aton("10.0.1.2")]

922+

)

923+

zc.registry.async_add(info)

924+925+

type_2 = "_otherknown2._tcp.local."

926+

name = "knownname"

927+

registration_name2 = f"{name}.{type_2}"

928+

desc = {'path': '/~paulsm/'}

929+

server_name2 = "ash-3.local."

930+

info2 = ServiceInfo(

931+

type_2, registration_name2, 80, 0, 0, desc, server_name2, addresses=[socket.inet_aton("10.0.1.2")]

932+

)

933+

zc.registry.async_add(info2)

934+

_clear_cache(zc)

935+936+

# Test PTR supression

937+

generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)

938+

question = r.DNSQuestion(const._SERVICE_TYPE_ENUMERATION_NAME.upper(), const._TYPE_PTR, const._CLASS_IN)

939+

generated.add_question(question)

940+

packets = generated.packets()

941+

question_answers = zc.query_handler.async_response([r.DNSIncoming(packet) for packet in packets], False)

942+

assert not question_answers.ucast

943+

assert not question_answers.mcast_now

944+

assert question_answers.mcast_aggregate

945+

assert not question_answers.mcast_aggregate_last_second

946+

# unregister

947+

zc.registry.async_remove(info)

948+

zc.registry.async_remove(info2)

949+

zc.close()

950+951+845952

# This test uses asyncio because it needs to access the cache directly

846953

# which is not threadsafe

847954

@pytest.mark.asyncio