@@ -340,6 +340,32 @@ def test_aaaa_query():
340340 zc .close ()
341341
342342
343+ @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' )
345371def test_a_and_aaaa_record_fate_sharing ():
@@ -481,6 +507,48 @@ async def test_probe_answered_immediately():
481507 zc .close ()
482508
483509
510+ @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+
484552def 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 ()
843911
844912
913+ 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
0 commit comments